--- /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 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
+ * 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
+ * The following example shows how create a recording that uses a predefined configuration.
+ *
+ *
+ * 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
+ * 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.
+ *
+ *
+ * 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
+ * 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
+ * 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 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);
+ }
+
+}
+ *
+ *
+ * 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.
+ *
+ * Recording r = new Recording(Configuration.getConfiguration("default"));
+ *
+ *
+ *
+ *
+ * The following example shows how to merge settings.
+ *
+ *
+ * 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
+ * Map
+ *
+ * @param settings the settings to set, not {@code null}
+ */
+ public void setSettings(MapmaxSize
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.
+ * 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.
+ *