--- /dev/null 2020-01-02 04:43:18.583999853 +0000 +++ new/src/share/classes/jdk/jfr/Recording.java 2020-01-30 06:25:08.115615293 +0000 @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2016, 2019, 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.HashMap; +import java.util.Map; +import java.util.Objects; + +import jdk.jfr.internal.PlatformRecorder; +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. + *

+ * The following example shows how configure, start, stop and dump recording data to disk. + * + *

+ * 
+ *   Configuration c = Configuration.getConfiguration("default");
+ *   Recording r = new Recording(c);
+ *   r.start();
+ *   System.gc();
+ *   Thread.sleep(5000);
+ *   r.stop();
+ *   r.dump(Files.createTempFile("my-recording", ".jfr"));
+ * 
+ * 
+ * + * @since 8 + */ +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 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 toMap() { + return recording.getSettings(); + } + } + + private final PlatformRecording internal; + + public Recording(Map settings) { + PlatformRecorder r = FlightRecorder.getFlightRecorder().getInternal(); + synchronized (r) { + this.internal = r.newRecording(settings); + this.internal.setRecording(this); + if (internal.getRecording() != this) { + throw new InternalError("Internal recording not properly setup"); + } + } + } + + /** + * Creates a recording without any settings. + *

+ * 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(new HashMap()); + } + + /** + * Creates a recording with settings from a configuration. + *

+ * The following example shows how create a recording that uses a predefined configuration. + * + *

+     * 
+     * Recording r = new Recording(Configuration.getConfiguration("default"));
+     * 
+     * 
+ * + * 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(configuration.getSettings()); + } + + /** + * Starts this recording. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * Modifying the returned {@code Map} will not change the settings for this recording. + *

+ * If no settings are set for this recording, an empty {@code Map} is + * returned. + * + * @return recording settings, not {@code null} + */ + public Map getSettings() { + return new HashMap<>(internal.getSettings()); + } + + /** + * Returns the current size of this recording in the disk repository, + * measured in bytes. + *

+ * 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. + *

+ * 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. + *

+ * The following example shows how to set event settings for a recording. + * + *

+     * 
+     *     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);
+     * 
+     * 
+ * + * The following example shows how to merge settings. + * + *
+     *     {@code
+     *     Map settings = recording.getSettings();
+     *     settings.putAll(additionalSettings);
+     *     recording.setSettings(settings);
+     * }
+     * 
+ * + * @param settings the settings to set, not {@code null} + */ + public void setSettings(Map settings) { + Objects.requireNonNull(settings); + Map 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. + *

+ * 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. + *

+ * 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.dump(new WriteableUserPath(destination)); + + } + + /** + * Returns {@code true} if this recording uses the disk repository, {@code false} otherwise. + *

+ * 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. + *

+ * 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. + *

+ * 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 maxSize 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. + *

+ * 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). + *

+ * 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 maxAge 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. + *

+ * If a destination is set, this recording is automatically closed + * after data is successfully copied to the destination path. + *

+ * If a destination is not 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 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 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); + } + +}