src/jdk.jfr/share/classes/jdk/jfr/Recording.java
changeset 50113 caf115bb98ad
child 50745 a390cbb82d47
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/Recording.java	Tue May 15 20:24:34 2018 +0200
@@ -0,0 +1,672 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import jdk.jfr.internal.PlatformRecording;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.Utils;
+import jdk.jfr.internal.WriteableUserPath;
+
+/**
+ * Provides means to configure, start, stop and dump recording data to disk.
+ * <p>
+ * The following example shows how configure, start, stop and dump recording data to disk.
+ *
+ * <pre>
+ * <code>
+ *   Configuration c = Configuration.getConfiguration("default");
+ *   Recording r = new Recording(c);
+ *   r.start();
+ *   System.gc();
+ *   Thread.sleep(5000);
+ *   r.stop();
+ *   r.copyTo(Files.createTempFile("my-recording", ".jfr"));
+ * </code>
+ * </pre>
+ *
+ * @since 9
+ */
+public final class Recording implements Closeable {
+
+    private static class RecordingSettings extends EventSettings {
+
+        private final Recording recording;
+        private final String identifier;
+
+        RecordingSettings(Recording r, String identifier) {
+            this.recording = r;
+            this.identifier = identifier;
+        }
+
+        RecordingSettings(Recording r, Class<? extends Event> eventClass) {
+            Utils.ensureValidEventSubclass(eventClass);
+            this.recording = r;
+            this.identifier = String.valueOf(Type.getTypeId(eventClass));
+        }
+
+        @Override
+        public EventSettings with(String name, String value) {
+            Objects.requireNonNull(value);
+            recording.setSetting(identifier + "#" + name, value);
+            return this;
+        }
+
+        @Override
+        public Map<String, String> toMap() {
+            return recording.getSettings();
+        }
+    }
+
+    private final PlatformRecording internal;
+
+    private Recording(PlatformRecording internal) {
+        this.internal = internal;
+        this.internal.setRecording(this);
+        if (internal.getRecording() != this) {
+            throw new InternalError("Internal recording not properly setup");
+        }
+    }
+
+    /**
+     * Creates a recording without any settings.
+     * <p>
+     * A newly created recording is in the {@link RecordingState#NEW} state. To start
+     * the recording, invoke the {@link Recording#start()} method.
+     *
+     * @throws IllegalStateException if Flight Recorder can't be created (for
+     *         example, if the Java Virtual Machine (JVM) lacks Flight Recorder
+     *         support, or if the file repository can't be created or accessed)
+     *
+     * @throws SecurityException If a security manager is used and
+     *         FlightRecorderPermission "accessFlightRecorder" is not set.
+     */
+    public Recording() {
+        this(FlightRecorder.getFlightRecorder().newInternalRecording(new HashMap<String, String>()));
+    }
+
+    /**
+     * Creates a recording with settings from a configuration.
+     * <p>
+     * The following example shows how create a recording that uses a predefined configuration.
+     *
+     * <pre>
+     * <code>
+     * Recording r = new Recording(Configuration.getConfiguration("default"));
+     * </code>
+     * </pre>
+     *
+     * The newly created recording is in the {@link RecordingState#NEW} state. To
+     * start the recording, invoke the {@link Recording#start()} method.
+     *
+     * @param configuration configuration that contains the settings to be use, not
+     *        {@code null}
+     *
+     * @throws IllegalStateException if Flight Recorder can't be created (for
+     *         example, if the Java Virtual Machine (JVM) lacks Flight Recorder
+     *         support, or if the file repository can't be created or accessed)
+     *
+     * @throws SecurityException if a security manager is used and
+     *         FlightRecorderPermission "accessFlightRecorder" is not set.
+     *
+     * @see Configuration
+     */
+    public Recording(Configuration configuration) {
+        this(FlightRecorder.getFlightRecorder().newInternalRecording(configuration.getSettings()));
+    }
+
+    /**
+     * Starts this recording.
+     * <p>
+     * It's recommended that the recording options and event settings are configured
+     * before calling this method. The benefits of doing so are a more consistent
+     * state when analyzing the recorded data, and improved performance because the
+     * configuration can be applied atomically.
+     * <p>
+     * After a successful invocation of this method, this recording is in the
+     * {@code RUNNING} state.
+     *
+     * @throws IllegalStateException if recording is already started or is in the
+     *         {@code CLOSED} state
+     */
+    public void start() {
+        internal.start();
+    }
+
+    /**
+     * Starts this recording after a delay.
+     * <p>
+     * After a successful invocation of this method, this recording is in the
+     * {@code DELAYED} state.
+     *
+     * @param delay the time to wait before starting this recording, not
+     *        {@code null}
+     * @throws IllegalStateException if the recording is not it the {@code NEW} state
+     */
+    public void scheduleStart(Duration delay) {
+        Objects.requireNonNull(delay);
+        internal.scheduleStart(delay);
+    }
+
+    /**
+     * Stops this recording.
+     * <p>
+     * When a recording is stopped it can't be restarted. If this
+     * recording has a destination, data is written to that destination and
+     * the recording is closed. After a recording is closed, the data is no longer
+     * available.
+     * <p>
+     * After a successful invocation of this method, this recording will be
+     * in the {@code STOPPED} state.
+     *
+     * @return {@code true} if recording is stopped, {@code false} otherwise
+     *
+     * @throws IllegalStateException if the recording is not started or is already stopped
+     *
+     * @throws SecurityException if a security manager exists and the caller
+     *         doesn't have {@code FilePermission} to write to the destination
+     *         path
+     *
+     * @see #setDestination(Path)
+     *
+     */
+    public boolean stop() {
+        return internal.stop("Stopped by user");
+    }
+
+    /**
+     * Returns settings used by this recording.
+     * <p>
+     * Modifying the returned {@code Map} will not change the settings for this recording.
+     * <p>
+     * If no settings are set for this recording, an empty {@code Map} is
+     * returned.
+     *
+     * @return recording settings, not {@code null}
+     */
+    public Map<String, String> getSettings() {
+        return new HashMap<>(internal.getSettings());
+    }
+
+    /**
+     * Returns the current size of this recording in the disk repository,
+     * measured in bytes.
+     * <p>
+     * The size is updated when recording buffers are flushed. If the recording is
+     * not written to the disk repository the returned size is always {@code 0}.
+     *
+     * @return amount of recorded data, measured in bytes, or {@code 0} if the
+     *         recording is not written to the disk repository
+     */
+    public long getSize() {
+        return internal.getSize();
+    }
+
+    /**
+     * Returns the time when this recording was stopped.
+     *
+     * @return the time, or {@code null} if this recording is not stopped
+     */
+    public Instant getStopTime() {
+        return internal.getStopTime();
+    }
+
+    /**
+     * Returns the time when this recording was started.
+     *
+     * @return the the time, or {@code null} if this recording is not started
+     */
+    public Instant getStartTime() {
+        return internal.getStartTime();
+    }
+
+    /**
+     * Returns the maximum size, measured in bytes, at which data is no longer kept in the disk repository.
+     *
+     * @return maximum size in bytes, or {@code 0} if no maximum size is set
+     */
+    public long getMaxSize() {
+        return internal.getMaxSize();
+    }
+
+    /**
+     * Returns the length of time that the data is kept in the disk repository
+     * before it is removed.
+     *
+     * @return maximum length of time, or {@code null} if no maximum length of time
+     *         has been set
+     */
+    public Duration getMaxAge() {
+        return internal.getMaxAge();
+    }
+
+    /**
+     * Returns the name of this recording.
+     * <p>
+     * By default, the name is the same as the recording ID.
+     *
+     * @return the recording name, not {@code null}
+     */
+    public String getName() {
+        return internal.getName();
+    }
+
+    /**
+     * Replaces all settings for this recording.
+     * <p>
+     * The following example shows how to set event settings for a recording.
+     *
+     * <pre>
+     * <code>
+     *     Map{@literal <}String, String{@literal >} settings = new HashMap{@literal <}{@literal >}();
+     *     settings.putAll(EventSettings.enabled("jdk.CPUSample").withPeriod(Duration.ofSeconds(2)).toMap());
+     *     settings.putAll(EventSettings.enabled(MyEvent.class).withThreshold(Duration.ofSeconds(2)).withoutStackTrace().toMap());
+     *     settings.put("jdk.ExecutionSample#period", "10 ms");
+     *     recording.setSettings(settings);
+     * </code>
+     * </pre>
+     *
+     * The following example shows how to merge settings.
+     *
+     * <pre>
+     *     {@code
+     *     Map<String, String> settings = recording.getSettings();
+     *     settings.putAll(additionalSettings);
+     *     recording.setSettings(settings);
+     * }
+     * </pre>
+     *
+     * @param settings the settings to set, not {@code null}
+     */
+    public void setSettings(Map<String, String> settings) {
+        Objects.requireNonNull(settings);
+        Map<String, String> sanitized = Utils.sanitizeNullFreeStringMap(settings);
+        internal.setSettings(sanitized);
+    }
+
+    /**
+     * Returns the recording state that this recording is currently in.
+     *
+     * @return the recording state, not {@code null}
+     *
+     * @see RecordingState
+     */
+    public RecordingState getState() {
+        return internal.getState();
+    }
+
+    /**
+     * Releases all data that is associated with this recording.
+     * <p>
+     * After a successful invocation of this method, this recording is in the
+     * {@code CLOSED} state.
+     */
+    @Override
+    public void close() {
+        internal.close();
+    }
+
+    /**
+     * Returns a clone of this recording, with a new recording ID and name.
+     *
+     * Clones are useful for dumping data without stopping the recording. After
+     * a clone is created, the amount of data to copy is constrained
+     * with the {@link #setMaxAge(Duration)} method and the {@link #setMaxSize(long)}method.
+     *
+     * @param stop {@code true} if the newly created copy should be stopped
+     *        immediately, {@code false} otherwise
+     * @return the recording copy, not {@code null}
+     */
+    public Recording copy(boolean stop) {
+        return internal.newCopy(stop);
+    }
+
+    /**
+     * Writes recording data to a file.
+     * <p>
+     * Recording must be started, but not necessarily stopped.
+     *
+     * @param destination the location where recording data is written, not
+     *        {@code null}
+     *
+     * @throws IOException if the recording can't be copied to the specified
+     *         location
+     *
+     * @throws SecurityException if a security manager exists and the caller doesn't
+     *         have {@code FilePermission} to write to the destination path
+     */
+    public void dump(Path destination) throws IOException {
+        Objects.requireNonNull(destination);
+        internal.copyTo(new WriteableUserPath(destination), "Dumped by user", Collections.emptyMap());
+    }
+
+    /**
+     * Returns {@code true} if this recording uses the disk repository, {@code false} otherwise.
+     * <p>
+     * If no value is set, {@code true} is returned.
+     *
+     * @return {@code true} if the recording uses the disk repository, {@code false}
+     *         otherwise
+     */
+    public boolean isToDisk() {
+        return internal.isToDisk();
+    }
+
+    /**
+     * Determines how much data is kept in the disk repository.
+     * <p>
+     * To control the amount of recording data that is stored on disk, the maximum
+     * amount of data to retain can be specified. When the maximum limit is
+     * exceeded, the Java Virtual Machine (JVM) removes the oldest chunk to make
+     * room for a more recent chunk.
+     * <p>
+     * If neither maximum limit or the maximum age is set, the size of the
+     * recording may grow indefinitely.
+     *
+     * @param maxSize the amount of data to retain, {@code 0} if infinite
+     *
+     * @throws IllegalArgumentException if <code>maxSize</code> is negative
+     *
+     * @throws IllegalStateException if the recording is in {@code CLOSED} state
+     */
+    public void setMaxSize(long maxSize) {
+        if (maxSize < 0) {
+            throw new IllegalArgumentException("Max size of recording can't be negative");
+        }
+        internal.setMaxSize(maxSize);
+    }
+
+    /**
+     * Determines how far back data is kept in the disk repository.
+     * <p>
+     * To control the amount of recording data stored on disk, the maximum length of
+     * time to retain the data can be specified. Data stored on disk that is older
+     * than the specified length of time is removed by the Java Virtual Machine (JVM).
+     * <p>
+     * If neither maximum limit or the maximum age is set, the size of the
+     * recording may grow indefinitely.
+     *
+     * @param maxAge the length of time that data is kept, or {@code null} if infinite
+     *
+     * @throws IllegalArgumentException if <code>maxAge</code> is negative
+     *
+     * @throws IllegalStateException if the recording is in the {@code CLOSED} state
+     */
+    public void setMaxAge(Duration maxAge) {
+        if (maxAge != null && maxAge.isNegative()) {
+            throw new IllegalArgumentException("Max age of recording can't be negative");
+        }
+        internal.setMaxAge(maxAge);
+    }
+
+    /**
+     * Sets a location where data is written on recording stop, or
+     * {@code null} if data is not to be dumped.
+     * <p>
+     * If a destination is set, this recording is automatically closed
+     * after data is successfully copied to the destination path.
+     * <p>
+     * If a destination is <em>not</em> set, Flight Recorder retains the
+     * recording data until this recording is closed. Use the {@link #dump(Path)} method to
+     * manually write data to a file.
+     *
+     * @param destination the destination path, or {@code null} if recording should
+     *        not be dumped at stop
+     *
+     * @throws IllegalStateException if recording is in the {@code STOPPED} or
+     *         {@code CLOSED} state.
+     *
+     * @throws SecurityException if a security manager exists and the caller
+     *         doesn't have {@code FilePermission} to read, write, and delete the
+     *         {@code destination} file
+     *
+     * @throws IOException if the path is not writable
+     */
+    public void setDestination(Path destination) throws IOException {
+        internal.setDestination(destination != null ? new WriteableUserPath(destination) : null);
+    }
+
+    /**
+     * Returns the destination file, where recording data is written when the
+     * recording stops, or {@code null} if no destination is set.
+     *
+     * @return the destination file, or {@code null} if not set.
+     */
+    public Path getDestination() {
+        WriteableUserPath usp = internal.getDestination();
+        if (usp == null) {
+            return null;
+        } else {
+            return usp.getPotentiallyMaliciousOriginal();
+        }
+    }
+
+    /**
+     * Returns a unique ID for this recording.
+     *
+     * @return the recording ID
+     */
+    public long getId() {
+        return internal.getId();
+    }
+
+    /**
+     * Sets a human-readable name (for example, {@code "My Recording"}).
+     *
+     * @param name the recording name, not {@code null}
+     *
+     * @throws IllegalStateException if the recording is in {@code CLOSED} state
+     */
+    public void setName(String name) {
+        Objects.requireNonNull(name);
+        internal.setName(name);
+    }
+
+    /**
+     * Sets whether this recording is dumped to disk when the JVM exits.
+     *
+     * @param dumpOnExit if this recording should be dumped when the JVM exits
+     */
+    public void setDumpOnExit(boolean dumpOnExit) {
+        internal.setDumpOnExit(dumpOnExit);
+    }
+
+    /**
+     * Returns whether this recording is dumped to disk when the JVM exits.
+     * <p>
+     * If dump on exit is not set, {@code false} is returned.
+     *
+     * @return {@code true} if the recording is dumped on exit, {@code false}
+     *         otherwise.
+     */
+    public boolean getDumpOnExit() {
+        return internal.getDumpOnExit();
+    }
+
+    /**
+     * Determines whether this recording is continuously flushed to the disk
+     * repository or data is constrained to what is available in memory buffers.
+     *
+     * @param disk {@code true} if this recording is written to disk,
+     *        {@code false} if in-memory
+     *
+     */
+    public void setToDisk(boolean disk) {
+        internal.setToDisk(disk);
+    }
+
+    /**
+     * Creates a data stream for a specified interval.
+     * <p>
+     * The stream may contain some data outside the specified range.
+     *
+     * @param the start start time for the stream, or {@code null} to get data from
+     *        start time of the recording
+     *
+     * @param the end end time for the stream, or {@code null} to get data until the
+     *        present time.
+     *
+     * @return an input stream, or {@code null} if no data is available in the
+     *         interval.
+     *
+     * @throws IllegalArgumentException if {@code end} happens before
+     *         {@code start}
+     *
+     * @throws IOException if a stream can't be opened
+     */
+    public InputStream getStream(Instant start, Instant end) throws IOException {
+        if (start != null && end != null && end.isBefore(start)) {
+            throw new IllegalArgumentException("End time of requested stream must not be before start time");
+        }
+        return internal.open(start, end);
+    }
+
+    /**
+     * Returns the specified duration for this recording, or {@code null} if no
+     * duration is set.
+     * <p>
+     * The duration can be set only when the recording is in the
+     * {@link RecordingState#NEW} state.
+     *
+     * @return the desired duration of the recording, or {@code null} if no duration
+     *         has been set.
+     */
+    public Duration getDuration() {
+        return internal.getDuration();
+    }
+
+    /**
+     * Sets a duration for how long a recording runs before it stops.
+     * <p>
+     * By default, a recording has no duration ({@code null}).
+     *
+     * @param duration the duration, or {@code null} if no duration is set
+     *
+     * @throws IllegalStateException if recording is in the {@code STOPPED} or {@code CLOSED} state
+     */
+    public void setDuration(Duration duration) {
+        internal.setDuration(duration);
+    }
+
+    /**
+     * Enables the event with the specified name.
+     * <p>
+     * If multiple events have the same name (for example, the same class is loaded
+     * in different class loaders), then all events that match the name are enabled. To
+     * enable a specific class, use the {@link #enable(Class)} method or a {@code String}
+     * representation of the event type ID.
+     *
+     * @param name the settings for the event, not {@code null}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     *
+     * @see EventType
+     */
+    public EventSettings enable(String name) {
+        Objects.requireNonNull(name);
+        RecordingSettings rs = new RecordingSettings(this, name);
+        rs.with("enabled", "true");
+        return rs;
+    }
+
+    /**
+     * Disables event with the specified name.
+     * <p>
+     * If multiple events with same name (for example, the same class is loaded
+     * in different class loaders), then all events that match the
+     * name is disabled. To disable a specific class, use the
+     * {@link #disable(Class)} method or a {@code String} representation of the event
+     * type ID.
+     *
+     * @param name the settings for the event, not {@code null}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     *
+     */
+    public EventSettings disable(String name) {
+        Objects.requireNonNull(name);
+        RecordingSettings rs = new RecordingSettings(this, name);
+        rs.with("enabled", "false");
+        return rs;
+    }
+
+    /**
+     * Enables event.
+     *
+     * @param eventClass the event to enable, not {@code null}
+     *
+     * @throws IllegalArgumentException if {@code eventClass} is an abstract
+     *         class or not a subclass of {@link Event}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     */
+    public EventSettings enable(Class<? extends Event> eventClass) {
+        Objects.requireNonNull(eventClass);
+        RecordingSettings rs = new RecordingSettings(this, eventClass);
+        rs.with("enabled", "true");
+        return rs;
+    }
+
+    /**
+     * Disables event.
+     *
+     * @param eventClass the event to enable, not {@code null}
+     *
+     * @throws IllegalArgumentException if {@code eventClass} is an abstract
+     *         class or not a subclass of {@link Event}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     *
+     */
+    public EventSettings disable(Class<? extends Event> eventClass) {
+        Objects.requireNonNull(eventClass);
+        RecordingSettings rs = new RecordingSettings(this, eventClass);
+        rs.with("enabled", "false");
+        return rs;
+    }
+
+    // package private
+    PlatformRecording getInternal() {
+        return internal;
+    }
+
+    private void setSetting(String id, String value) {
+        Objects.requireNonNull(id);
+        Objects.requireNonNull(value);
+        internal.setSetting(id, value);
+    }
+
+}