src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java
changeset 50745 a390cbb82d47
parent 50197 f4735ff8d17d
child 52413 6372f5af9612
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Mon Jun 25 02:07:42 2018 +0200
@@ -41,7 +41,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -54,6 +53,7 @@
 import jdk.jfr.FlightRecorderListener;
 import jdk.jfr.Recording;
 import jdk.jfr.RecordingState;
+import jdk.jfr.internal.SecuritySupport.SafePath;
 
 public final class PlatformRecording implements AutoCloseable {
 
@@ -70,6 +70,7 @@
     private boolean toDisk = true;
     private String name;
     private boolean dumpOnExit;
+    private SafePath dumpOnExitDirectory = new SafePath(".");
     // Timestamp information
     private Instant stopTime;
     private Instant startTime;
@@ -89,7 +90,7 @@
         // when you call dump(Path) or setDdestination(Path),
         // but if no destination is set and dumponexit=true
         // the control context of the recording is taken when the
-        // Recording object is constructed.  This works well for
+        // Recording object is constructed. This works well for
         // -XX:StartFlightRecording and JFR.dump
         this.noDestinationDumpOnExitAccessControlContext = AccessController.getContext();
         this.id = id;
@@ -114,45 +115,37 @@
             Logger.log(LogTag.JFR, LogLevel.INFO, () -> {
                 // Only print non-default values so it easy to see
                 // which options were added
-                    StringJoiner options = new StringJoiner(", ");
-                    if (!toDisk) {
-                        options.add("disk=false");
-                    }
-                    if (maxAge != null) {
-                        options.add("maxage=" + Utils.formatTimespan(maxAge, ""));
-                    }
-                    if (maxSize != 0) {
-                        options.add("maxsize=" + Utils.formatBytes(maxSize, ""));
-                    }
-                    if (dumpOnExit) {
-                        options.add("dumponexit=true");
-                    }
-                    if (duration != null) {
-                        options.add("duration=" + Utils.formatTimespan(duration, ""));
-                    }
-                    if (destination != null) {
-                        options.add("filename=" + destination.getText());
-                    }
-                    String optionText = options.toString();
-                    if (optionText.length() != 0) {
-                        optionText = "{" + optionText + "}";
-                    }
-                    return "Started recording \"" + getName() + "\" (" + getId() + ") " + optionText;
-                });
+                StringJoiner options = new StringJoiner(", ");
+                if (!toDisk) {
+                    options.add("disk=false");
+                }
+                if (maxAge != null) {
+                    options.add("maxage=" + Utils.formatTimespan(maxAge, ""));
+                }
+                if (maxSize != 0) {
+                    options.add("maxsize=" + Utils.formatBytes(maxSize, ""));
+                }
+                if (dumpOnExit) {
+                    options.add("dumponexit=true");
+                }
+                if (duration != null) {
+                    options.add("duration=" + Utils.formatTimespan(duration, ""));
+                }
+                if (destination != null) {
+                    options.add("filename=" + destination.getText());
+                }
+                String optionText = options.toString();
+                if (optionText.length() != 0) {
+                    optionText = "{" + optionText + "}";
+                }
+                return "Started recording \"" + getName() + "\" (" + getId() + ") " + optionText;
+            });
             newState = getState();
         }
         notifyIfStateChanged(oldState, newState);
     }
 
     public boolean stop(String reason) {
-        return stop(reason, null);
-    }
-
-    public boolean stop(String reason, WriteableUserPath alternativePath) {
-        return stop(reason, alternativePath, Collections.emptyMap());
-    }
-
-    boolean stop(String reason, WriteableUserPath alternativePath, Map<String, String> overlaySettings) {
         RecordingState oldState;
         RecordingState newState;
         synchronized (recorder) {
@@ -161,23 +154,21 @@
                 stopTask.cancel();
                 stopTask = null;
             }
-            recorder.stop(this, alternativePath);
+            recorder.stop(this);
             String endText = reason == null ? "" : ". Reason \"" + reason + "\".";
-            Logger.log(LogTag.JFR, LogLevel.INFO, "Stopped recording \"" + getName() + "\" (" + getId()+ ")" + endText);
+            Logger.log(LogTag.JFR, LogLevel.INFO, "Stopped recording \"" + getName() + "\" (" + getId() + ")" + endText);
             this.stopTime = Instant.now();
             newState = getState();
         }
         WriteableUserPath dest = getDestination();
-        if (dest == null && alternativePath != null) {
-            dest = alternativePath;
-        }
+
         if (dest != null) {
             try {
-                copyTo(dest, reason, overlaySettings);
-                Logger.log(LogTag.JFR, LogLevel.INFO, "Wrote recording \"" + getName() + "\" (" + getId()+ ") to " + dest.getText());
+                dumpStopped(dest);
+                Logger.log(LogTag.JFR, LogLevel.INFO, "Wrote recording \"" + getName() + "\" (" + getId() + ") to " + dest.getText());
                 notifyIfStateChanged(newState, oldState);
                 close(); // remove if copied out
-            } catch (IOException e) {
+            } catch(IOException e) {
                 // throw e; // BUG8925030
             }
         } else {
@@ -195,7 +186,7 @@
             setState(RecordingState.DELAYED);
             startTask = createStartTask();
             recorder.getTimer().schedule(startTask, delay.toMillis());
-            Logger.log(LogTag.JFR, LogLevel.INFO, "Scheduled recording \"" + getName() + "\" (" + getId()+ ") to start at " + now);
+            Logger.log(LogTag.JFR, LogLevel.INFO, "Scheduled recording \"" + getName() + "\" (" + getId() + ") to start at " + now);
         }
     }
 
@@ -271,7 +262,6 @@
         }
     }
 
-
     public RecordingState getState() {
         synchronized (recorder) {
             return state;
@@ -296,79 +286,67 @@
                 }
                 chunks.clear();
                 setState(RecordingState.CLOSED);
-                Logger.log(LogTag.JFR, LogLevel.INFO, "Closed recording \"" + getName() + "\" (" + getId()+ ")");
+                Logger.log(LogTag.JFR, LogLevel.INFO, "Closed recording \"" + getName() + "\" (" + getId() + ")");
             }
             newState = getState();
         }
         notifyIfStateChanged(newState, oldState);
     }
 
-    public void copyTo(WriteableUserPath path, String reason, Map<String, String> dumpSettings) throws IOException {
-        synchronized (recorder) {
-            RecordingState state = getState();
-            if (state == RecordingState.CLOSED) {
-                throw new IOException("Recording \"" + name + "\" (id=" + id + ") has been closed, no contents to write");
+    // To be used internally when doing dumps.
+    // Caller must have recorder lock and close recording before releasing lock
+    public PlatformRecording newSnapshotClone(String reason, Boolean pathToGcRoots) throws IOException {
+        if(!Thread.holdsLock(recorder)) {
+            throw new InternalError("Caller must have recorder lock");
+        }
+        RecordingState state = getState();
+        if (state == RecordingState.CLOSED) {
+            throw new IOException("Recording \"" + name + "\" (id=" + id + ") has been closed, no contents to write");
+        }
+        if (state == RecordingState.DELAYED || state == RecordingState.NEW) {
+            throw new IOException("Recording \"" + name + "\" (id=" + id + ") has not started, no contents to write");
+        }
+        if (state == RecordingState.STOPPED) {
+            PlatformRecording clone = recorder.newTemporaryRecording();
+            for (RepositoryChunk r : chunks) {
+                clone.add(r);
             }
-            if (state == RecordingState.DELAYED || state == RecordingState.NEW) {
-                throw new IOException("Recording \"" + name + "\" (id=" + id + ") has not started, no contents to write");
-            }
-            if (state == RecordingState.STOPPED) {
-                // have all we need, just write it
-                dumpToFile(path, reason, getId());
-                return;
-            }
+            return clone;
+        }
 
-            // Recording is RUNNING, create a clone
-            try(PlatformRecording clone = recorder.newRecording(Collections.emptyMap(), 0)) {
-                clone.setShouldWriteActiveRecordingEvent(false);
-                clone.setName(getName());
-                clone.setDestination(path);
-                clone.setToDisk(true);
-                // We purposely don't clone settings, since
-                // a union a == a
-                if (!isToDisk()) {
-                    // force memory contents to disk
-                    clone.start();
-                } else {
-                    // using existing chunks on disk
-                    for (RepositoryChunk c : chunks) {
-                        clone.add(c);
-                    }
-                    clone.setState(RecordingState.RUNNING);
-                    clone.setStartTime(getStartTime());
-                }
-                if (dumpSettings.isEmpty()) {
-                    clone.setSettings(getSettings());
-                    clone.stop(reason); // dumps to destination path here
-                } else {
-                    // Risk of violating lock order here, since
-                    // clone.stop() will take recorder lock inside
-                    // metadata lock, but OK if we already
-                    // have recorder lock when we entered metadata lock
-                    Thread.holdsLock(recorder);
-                    synchronized(MetadataRepository.getInstance()) {
-                        Thread.holdsLock(recorder);
-                        Map<String, String> oldSettings = getSettings();
-                        Map<String, String> newSettings = new HashMap<>(oldSettings);
-                        // replace with dump settings
-                        newSettings.putAll(dumpSettings);
-                        clone.setSettings(newSettings);
-                        clone.stop(reason);
-                    }
-                }
+        // Recording is RUNNING, create a clone
+        PlatformRecording clone = recorder.newTemporaryRecording();
+        clone.setShouldWriteActiveRecordingEvent(false);
+        clone.setName(getName());
+        clone.setDestination(this.destination);
+        clone.setToDisk(true);
+        // We purposely don't clone settings here, since
+        // a union a == a
+        if (!isToDisk()) {
+            // force memory contents to disk
+            clone.start();
+        } else {
+            // using existing chunks on disk
+            for (RepositoryChunk c : chunks) {
+                clone.add(c);
             }
-            return;
+            clone.setState(RecordingState.RUNNING);
+            clone.setStartTime(getStartTime());
         }
-    }
-
-    private void dumpToFile(WriteableUserPath userPath, String reason, long id) throws IOException {
-        userPath.doPriviligedIO(() -> {
-            try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
-                cc.transferTo(fc);
-                fc.force(true);
+        if (pathToGcRoots == null) {
+            clone.setSettings(getSettings()); // needed for old object sample
+            clone.stop(reason); // dumps to destination path here
+        } else {
+            // Risk of violating lock order here, since
+            // clone.stop() will take recorder lock inside
+            // metadata lock, but OK if we already
+            // have recorder lock when we entered metadata lock
+            synchronized (MetadataRepository.getInstance()) {
+                clone.setSettings(OldObjectSample.createSettingsForSnapshot(this, pathToGcRoots));
+                clone.stop(reason);
             }
-            return null;
-        });
+        }
+        return clone;
     }
 
     public boolean isToDisk() {
@@ -387,7 +365,7 @@
         }
     }
 
-   public void setDestination(WriteableUserPath userSuppliedPath) throws IOException {
+    public void setDestination(WriteableUserPath userSuppliedPath) throws IOException {
         synchronized (recorder) {
             if (Utils.isState(getState(), RecordingState.STOPPED, RecordingState.CLOSED)) {
                 throw new IllegalStateException("Destination can't be set on a recording that has been stopped/closed");
@@ -479,7 +457,7 @@
             TreeMap<String, String> ordered = new TreeMap<>(settings);
             Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "New settings for recording \"" + getName() + "\" (" + getId() + ")");
             for (Map.Entry<String, String> entry : ordered.entrySet()) {
-                String text =  entry.getKey() + "=\"" + entry.getValue() + "\"";
+                String text = entry.getKey() + "=\"" + entry.getValue() + "\"";
                 Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, text);
             }
         }
@@ -491,7 +469,6 @@
         }
     }
 
-
     private void notifyIfStateChanged(RecordingState newState, RecordingState oldState) {
         if (oldState == newState) {
             return;
@@ -580,12 +557,12 @@
     private void added(RepositoryChunk c) {
         c.use();
         size += c.getSize();
-        Logger.log(JFR, DEBUG,  ()-> "Recording \"" + name + "\" (" + id + ") added chunk " + c.toString() + ", current size=" + size);
+        Logger.log(JFR, DEBUG, () -> "Recording \"" + name + "\" (" + id + ") added chunk " + c.toString() + ", current size=" + size);
     }
 
     private void removed(RepositoryChunk c) {
         size -= c.getSize();
-        Logger.log(JFR, DEBUG,  ()->  "Recording \"" + name + "\" (" + id + ") removed chunk " + c.toString() + ", current size=" + size);
+        Logger.log(JFR, DEBUG, () -> "Recording \"" + name + "\" (" + id + ") removed chunk " + c.toString() + ", current size=" + size);
         c.release();
     }
 
@@ -661,7 +638,7 @@
                     stop("End of duration reached");
                 } catch (Throwable t) {
                     // Prevent malicious user to propagate exception callback in the wrong context
-                    Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not stop recording. " + t.getMessage());
+                    Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not stop recording.");
                 }
             }
         };
@@ -678,7 +655,7 @@
     }
 
     void clearDestination() {
-       destination = null;
+        destination = null;
     }
 
     public AccessControlContext getNoDestinationDumpOnExitAccessControlContext() {
@@ -686,10 +663,116 @@
     }
 
     void setShouldWriteActiveRecordingEvent(boolean shouldWrite) {
-       this.shuoldWriteActiveRecordingEvent = shouldWrite;
+        this.shuoldWriteActiveRecordingEvent = shouldWrite;
     }
 
     boolean shouldWriteMetadataEvent() {
         return shuoldWriteActiveRecordingEvent;
     }
+
+    // Dump running and stopped recordings
+    public void dump(WriteableUserPath writeableUserPath) throws IOException {
+        synchronized (recorder) {
+            try(PlatformRecording p = newSnapshotClone("Dumped by user", null))  {
+                p.dumpStopped(writeableUserPath);
+            }
+        }
+    }
+
+    public void dumpStopped(WriteableUserPath userPath) throws IOException {
+        synchronized (recorder) {
+                userPath.doPriviligedIO(() -> {
+                    try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
+                        cc.transferTo(fc);
+                        fc.force(true);
+                    }
+                    return null;
+                });
+        }
+    }
+
+    public void filter(Instant begin, Instant end, Long maxSize) {
+        synchronized (recorder) {
+            List<RepositoryChunk> result = removeAfter(end, removeBefore(begin, new ArrayList<>(chunks)));
+            if (maxSize != null) {
+                if (begin != null && end == null) {
+                    result = reduceFromBeginning(maxSize, result);
+                } else {
+                    result = reduceFromEnd(maxSize, result);
+                }
+            }
+            int size = 0;
+            for (RepositoryChunk r : result) {
+                size += r.getSize();
+                r.use();
+            }
+            this.size = size;
+            for (RepositoryChunk r : chunks) {
+                r.release();
+            }
+            chunks.clear();
+            chunks.addAll(result);
+        }
+    }
+
+    private static List<RepositoryChunk> removeBefore(Instant time, List<RepositoryChunk> input) {
+        if (time == null) {
+            return input;
+        }
+        List<RepositoryChunk> result = new ArrayList<>(input.size());
+        for (RepositoryChunk r : input) {
+            if (!r.getEndTime().isBefore(time)) {
+                result.add(r);
+            }
+        }
+        return result;
+    }
+
+    private static List<RepositoryChunk> removeAfter(Instant time, List<RepositoryChunk> input) {
+        if (time == null) {
+            return input;
+        }
+        List<RepositoryChunk> result = new ArrayList<>(input.size());
+        for (RepositoryChunk r : input) {
+            if (!r.getStartTime().isAfter(time)) {
+                result.add(r);
+            }
+        }
+        return result;
+    }
+
+    private static List<RepositoryChunk> reduceFromBeginning(Long maxSize, List<RepositoryChunk> input) {
+        if (maxSize == null || input.isEmpty()) {
+            return input;
+        }
+        List<RepositoryChunk> result = new ArrayList<>(input.size());
+        long total = 0;
+        for (RepositoryChunk r : input) {
+            total += r.getSize();
+            if (total > maxSize) {
+                break;
+            }
+            result.add(r);
+        }
+        // always keep at least one chunk
+        if (result.isEmpty()) {
+            result.add(input.get(0));
+        }
+        return result;
+    }
+
+    private static List<RepositoryChunk> reduceFromEnd(Long maxSize, List<RepositoryChunk> input) {
+        Collections.reverse(input);
+        List<RepositoryChunk> result = reduceFromBeginning(maxSize, input);
+        Collections.reverse(result);
+        return result;
+    }
+
+    public void setDumpOnExitDirectory(SafePath directory) {
+       this.dumpOnExitDirectory = directory;
+    }
+
+    public SafePath getDumpOnExitDirectory()  {
+        return this.dumpOnExitDirectory;
+    }
 }