# HG changeset patch # User egahlin # Date 1529885262 -7200 # Node ID a390cbb82d476dad666955f5751bab467aa19e39 # Parent 6c306d54366d7d33b8f511bc8367529e3f398e2f 8203929: Limit amount of data for JFR.dump Reviewed-by: mgronlun diff -r 6c306d54366d -r a390cbb82d47 src/hotspot/share/jfr/dcmd/jfrDcmds.cpp --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp Sun Jun 24 16:25:47 2018 +0100 +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp Mon Jun 25 02:07:42 2018 +0200 @@ -179,11 +179,19 @@ JfrDumpFlightRecordingDCmd::JfrDumpFlightRecordingDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), - _name("name", "Recording name, e.g. \\\"My Recording\\\"", "STRING", true, NULL), - _filename("filename", "Copy recording data to file, i.e \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", true), + _name("name", "Recording name, e.g. \\\"My Recording\\\"", "STRING", false, NULL), + _filename("filename", "Copy recording data to file, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false), + _maxage("maxage", "Maximum duration to dump, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit", "NANOTIME", false, "0"), + _maxsize("maxsize", "Maximum amount of bytes to dump, in (M)B or (G)B, e.g. 500M, or 0 for no limit", "MEMORY SIZE", false, "0"), + _begin("begin", "Point in time to dump data from, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d", "STRING", false), + _end("end", "Point in time to dump data to, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d", "STRING", false), _path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") { _dcmdparser.add_dcmd_option(&_name); _dcmdparser.add_dcmd_option(&_filename); + _dcmdparser.add_dcmd_option(&_maxage); + _dcmdparser.add_dcmd_option(&_maxsize); + _dcmdparser.add_dcmd_option(&_begin); + _dcmdparser.add_dcmd_option(&_end); _dcmdparser.add_dcmd_option(&_path_to_gc_roots); }; @@ -225,6 +233,26 @@ filepath = JfrJavaSupport::new_string(_filename.value(), CHECK); } + jobject maxage = NULL; + if (_maxage.is_set()) { + maxage = JfrJavaSupport::new_java_lang_Long(_maxage.value()._nanotime, CHECK); + } + + jobject maxsize = NULL; + if (_maxsize.is_set()) { + maxsize = JfrJavaSupport::new_java_lang_Long(_maxsize.value()._size, CHECK); + } + + jstring begin = NULL; + if (_begin.is_set() && _begin.value() != NULL) { + begin = JfrJavaSupport::new_string(_begin.value(), CHECK); + } + + jstring end = NULL; + if (_end.is_set() && _end.value() != NULL) { + end = JfrJavaSupport::new_string(_end.value(), CHECK); + } + jobject path_to_gc_roots = NULL; if (_path_to_gc_roots.is_set()) { path_to_gc_roots = JfrJavaSupport::new_java_lang_Boolean(_path_to_gc_roots.value(), CHECK); @@ -232,7 +260,7 @@ static const char klass[] = "jdk/jfr/internal/dcmd/DCmdDump"; static const char method[] = "execute"; - static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String;"; + static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String;"; JfrJavaArguments execute_args(&result, klass, method, signature, CHECK); execute_args.set_receiver(h_dcmd_instance); @@ -240,6 +268,10 @@ // arguments execute_args.push_jobject(name); execute_args.push_jobject(filepath); + execute_args.push_jobject(maxage); + execute_args.push_jobject(maxsize); + execute_args.push_jobject(begin); + execute_args.push_jobject(end); execute_args.push_jobject(path_to_gc_roots); JfrJavaSupport::call_virtual(&execute_args, THREAD); @@ -247,7 +279,7 @@ } JfrCheckFlightRecordingDCmd::JfrCheckFlightRecordingDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap), - _name("name","Recording text, e.g. \\\"My Recording\\\" or omit to see all recordings","STRING",false, NULL), + _name("name","Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings","STRING",false, NULL), _verbose("verbose","Print event settings for the recording(s)","BOOLEAN", false, "false") { _dcmdparser.add_dcmd_option(&_name); diff -r 6c306d54366d -r a390cbb82d47 src/hotspot/share/jfr/dcmd/jfrDcmds.hpp --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp Sun Jun 24 16:25:47 2018 +0100 +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp Mon Jun 25 02:07:42 2018 +0200 @@ -31,6 +31,10 @@ protected: DCmdArgument _name; DCmdArgument _filename; + DCmdArgument _maxage; + DCmdArgument _maxsize; + DCmdArgument _begin; + DCmdArgument _end; DCmdArgument _path_to_gc_roots; public: diff -r 6c306d54366d -r a390cbb82d47 src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp --- a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp Sun Jun 24 16:25:47 2018 +0100 +++ b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp Mon Jun 25 02:07:42 2018 +0200 @@ -55,7 +55,8 @@ JFR_LOG_TAG(jfr, system, metadata) \ JFR_LOG_TAG(jfr, metadata) \ JFR_LOG_TAG(jfr, event) \ - JFR_LOG_TAG(jfr, setting) + JFR_LOG_TAG(jfr, setting) \ + JFR_LOG_TAG(jfr, dcmd) /* NEW TAGS, DONT FORGET TO UPDATE JAVA SIDE */ #endif // SHARE_VM_JFR_UTILITIES_JFRLOGTAGSETS_HPP diff -r 6c306d54366d -r a390cbb82d47 src/hotspot/share/logging/logTag.hpp --- a/src/hotspot/share/logging/logTag.hpp Sun Jun 24 16:25:47 2018 +0100 +++ b/src/hotspot/share/logging/logTag.hpp Mon Jun 25 02:07:42 2018 +0200 @@ -44,6 +44,7 @@ LOG_TAG(blocks) \ LOG_TAG(bot) \ LOG_TAG(breakpoint) \ + LOG_TAG(bytecode) \ LOG_TAG(cds) \ LOG_TAG(census) \ LOG_TAG(class) \ @@ -60,11 +61,13 @@ LOG_TAG(cset) \ LOG_TAG(data) \ LOG_TAG(datacreation) \ + LOG_TAG(dcmd) \ LOG_TAG(decoder) \ LOG_TAG(defaultmethods) \ LOG_TAG(director) \ LOG_TAG(dump) \ LOG_TAG(ergo) \ + LOG_TAG(event) \ LOG_TAG(exceptions) \ LOG_TAG(exit) \ LOG_TAG(fingerprint) \ @@ -81,6 +84,7 @@ LOG_TAG(inlining) \ LOG_TAG(interpreter) \ LOG_TAG(itables) \ + LOG_TAG(jfr) \ LOG_TAG(jit) \ LOG_TAG(jni) \ LOG_TAG(jvmti) \ @@ -105,6 +109,7 @@ LOG_TAG(normalize) \ LOG_TAG(objecttagging) \ LOG_TAG(obsolete) \ + LOG_TAG(oldobject) \ LOG_TAG(oom) \ LOG_TAG(oopmap) \ LOG_TAG(oops) \ @@ -126,10 +131,13 @@ LOG_TAG(region) \ LOG_TAG(reloc) \ LOG_TAG(remset) \ + LOG_TAG(parser) \ LOG_TAG(purge) \ LOG_TAG(resolve) \ LOG_TAG(safepoint) \ + LOG_TAG(sampling) \ LOG_TAG(scavenge) \ + LOG_TAG(setting) \ LOG_TAG(smr) \ LOG_TAG(stacktrace) \ LOG_TAG(stackwalk) \ @@ -143,6 +151,7 @@ LOG_TAG(subclass) \ LOG_TAG(survivor) \ LOG_TAG(sweep) \ + LOG_TAG(system) \ LOG_TAG(table) \ LOG_TAG(task) \ DEBUG_ONLY(LOG_TAG(test)) \ @@ -160,15 +169,7 @@ LOG_TAG(vmoperation) \ LOG_TAG(vmthread) \ LOG_TAG(vtables) \ - LOG_TAG(workgang) \ - LOG_TAG(jfr) \ - LOG_TAG(system) \ - LOG_TAG(parser) \ - LOG_TAG(bytecode) \ - LOG_TAG(setting) \ - LOG_TAG(oldobject) \ - LOG_TAG(sampling) \ - LOG_TAG(event) + LOG_TAG(workgang) LOG_TAG_LIST_EXT #define PREFIX_LOG_TAG(T) (LogTag::_##T) diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java --- a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java Mon Jun 25 02:07:42 2018 +0200 @@ -34,7 +34,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Objects; import jdk.jfr.internal.JVM; @@ -77,8 +76,8 @@ */ public List getRecordings() { List recs = new ArrayList<>(); - for (PlatformRecording internal : internal.getRecordings()) { - recs.add(internal.getRecording()); + for (PlatformRecording r : internal.getRecordings()) { + recs.add(r.getRecording()); } return Collections.unmodifiableList(recs); } @@ -112,7 +111,10 @@ * @return a snapshot of all available recording data, not {@code null} */ public Recording takeSnapshot() { - return internal.newSnapshot(); + Recording snapshot = new Recording(); + snapshot.setName("Snapshot"); + internal.fillWithRecordedData(snapshot.getInternal(), null); + return snapshot; } /** @@ -345,9 +347,7 @@ return initialized; } - // package private - PlatformRecording newInternalRecording(Map settings) { - return internal.newRecording(settings); + PlatformRecorder getInternal() { + return internal; } - } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/FlightRecorderPermission.java --- a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorderPermission.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorderPermission.java Mon Jun 25 02:07:42 2018 +0200 @@ -31,6 +31,7 @@ import java.util.Objects; import jdk.jfr.internal.PlatformEventType; +import jdk.jfr.internal.PlatformRecorder; import jdk.jfr.internal.PlatformRecording; import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.Type; @@ -185,6 +186,11 @@ public boolean isUnsigned(ValueDescriptor v) { return v.isUnsigned(); } + + @Override + public PlatformRecorder getPlatformRecorder() { + return FlightRecorder.getFlightRecorder().getInternal(); + } } /** diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/Recording.java --- a/src/jdk.jfr/share/classes/jdk/jfr/Recording.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/Recording.java Mon Jun 25 02:07:42 2018 +0200 @@ -31,11 +31,11 @@ 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.PlatformRecorder; import jdk.jfr.internal.PlatformRecording; import jdk.jfr.internal.Type; import jdk.jfr.internal.Utils; @@ -93,11 +93,14 @@ 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"); + 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"); + } } } @@ -115,8 +118,8 @@ * FlightRecorderPermission "accessFlightRecorder" is not set. */ public Recording() { - this(FlightRecorder.getFlightRecorder().newInternalRecording(new HashMap())); - } + this(new HashMap()); + } /** * Creates a recording with settings from a configuration. @@ -145,7 +148,7 @@ * @see Configuration */ public Recording(Configuration configuration) { - this(FlightRecorder.getFlightRecorder().newInternalRecording(configuration.getSettings())); + this(configuration.getSettings()); } /** @@ -370,7 +373,8 @@ */ public void dump(Path destination) throws IOException { Objects.requireNonNull(destination); - internal.copyTo(new WriteableUserPath(destination), "Dumped by user", Collections.emptyMap()); + internal.dump(new WriteableUserPath(destination)); + } /** diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java Mon Jun 25 02:07:42 2018 +0200 @@ -73,7 +73,12 @@ /** * Covers setting (for users of the JDK) */ - JFR_SETTING(9); + JFR_SETTING(9), + /** + * Covers usage of jcmd with JFR + */ + JFR_DCMD(10); + /* set from native side */ private volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized @@ -86,4 +91,8 @@ public boolean shouldLog(int level) { return level >= tagSetLevel; } + + public boolean shouldLog(LogLevel logLevel) { + return shouldLog(logLevel.level); + } } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java Mon Jun 25 02:07:42 2018 +0200 @@ -0,0 +1,73 @@ +package jdk.jfr.internal; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jdk.jfr.Enabled; +import jdk.jfr.RecordingState; +import jdk.jfr.internal.settings.CutoffSetting; +import jdk.jfr.internal.test.WhiteBox; + +// The Old Object event could have been implemented as a periodic event, but +// due to chunk rotations and how settings are calculated when multiple recordings +// are running at the same time, it would lead to unacceptable overhead. +// +// Instead, the event is only emitted before a recording stops and +// if that recording has the event enabled. +// +// This requires special handling and the purpose of this class is to provide that +// +public final class OldObjectSample { + + private static final String EVENT_NAME = Type.EVENT_NAME_PREFIX + "OldObjectSample"; + private static final String OLD_OBJECT_CUTOFF = EVENT_NAME + "#" + Cutoff.NAME; + private static final String OLD_OBJECT_ENABLED = EVENT_NAME + "#" + Enabled.NAME; + + // Emit if old object is enabled in recoding with cutoff for that recording + public static void emit(PlatformRecording recording) { + if (isEnabled(recording)) { + long nanos = CutoffSetting.parseValueSafe(recording.getSettings().get(OLD_OBJECT_CUTOFF)); + long ticks = Utils.nanosToTicks(nanos); + JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples()); + } + } + + // Emit if old object is enabled for at least one recording, and use the largest + // cutoff for an enabled recoding + public static void emit(List recordings, Boolean pathToGcRoots) { + boolean enabled = false; + long cutoffNanos = Boolean.TRUE.equals(pathToGcRoots) ? Long.MAX_VALUE : 0L; + for (PlatformRecording r : recordings) { + if (r.getState() == RecordingState.RUNNING) { + if (isEnabled(r)) { + enabled = true; + long c = CutoffSetting.parseValueSafe(r.getSettings().get(OLD_OBJECT_CUTOFF)); + cutoffNanos = Math.max(c, cutoffNanos); + } + } + } + if (enabled) { + long ticks = Utils.nanosToTicks(cutoffNanos); + JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples()); + } + } + + public static void updateSettingPathToGcRoots(Map s, Boolean pathToGcRoots) { + if (pathToGcRoots != null) { + s.put(OLD_OBJECT_CUTOFF, pathToGcRoots ? "infinity" : "0 ns"); + } + } + + public static Map createSettingsForSnapshot(PlatformRecording recording, Boolean pathToGcRoots) { + Map settings = new HashMap<>(recording.getSettings()); + updateSettingPathToGcRoots(settings, pathToGcRoots); + return settings; + } + + private static boolean isEnabled(PlatformRecording r) { + Map settings = r.getSettings(); + String s = settings.get(OLD_OBJECT_ENABLED); + return "true".equals(s); + } +} diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java Mon Jun 25 02:07:42 2018 +0200 @@ -38,6 +38,7 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -46,7 +47,6 @@ import java.util.TimerTask; import java.util.concurrent.CopyOnWriteArrayList; -import jdk.jfr.Enabled; import jdk.jfr.EventType; import jdk.jfr.FlightRecorder; import jdk.jfr.FlightRecorderListener; @@ -56,8 +56,6 @@ import jdk.jfr.events.ActiveSettingEvent; import jdk.jfr.internal.SecuritySupport.SecureRecorderListener; import jdk.jfr.internal.instrument.JDKEvents; -import jdk.jfr.internal.settings.CutoffSetting; -import jdk.jfr.internal.test.WhiteBox; public final class PlatformRecorder { @@ -111,7 +109,16 @@ return newRecording(settings, ++recordingCounter); } - public synchronized PlatformRecording newRecording(Map settings, long id) { + // To be used internally when doing dumps. + // Caller must have recorder lock and close recording before releasing lock + public PlatformRecording newTemporaryRecording() { + if(!Thread.holdsLock(this)) { + throw new InternalError("Caller must have recorder lock"); + } + return newRecording(new HashMap<>(), 0); + } + + private synchronized PlatformRecording newRecording(Map settings, long id) { PlatformRecording recording = new PlatformRecording(this, id); if (!settings.isEmpty()) { recording.setSettings(settings); @@ -249,7 +256,7 @@ RequestEngine.doChunkBegin(); } - synchronized void stop(PlatformRecording recording, WriteableUserPath alternativePath) { + synchronized void stop(PlatformRecording recording) { RecordingState state = recording.getState(); if (Utils.isAfter(state, RecordingState.RUNNING)) { @@ -270,7 +277,7 @@ } } } - emitOldObjectSamples(recording); + OldObjectSample.emit(recording); if (endPhysical) { RequestEngine.doChunkEnd(); @@ -282,7 +289,7 @@ } } else { // last memory - dumpMemoryToDestination(recording, alternativePath); + dumpMemoryToDestination(recording); } jvm.endRecording_(); disableEvents(); @@ -306,26 +313,13 @@ recording.setState(RecordingState.STOPPED); } - // Only dump event if the recording that is being stopped - // has OldObjectSample enabled + cutoff from recording, not global value - private void emitOldObjectSamples(PlatformRecording recording) { - Map settings = recording.getSettings(); - String s = settings.get(Type.EVENT_NAME_PREFIX + "OldObjectSample#" + Enabled.NAME); - if ("true".equals(s)) { - long nanos = CutoffSetting.parseValueSafe(settings.get(Type.EVENT_NAME_PREFIX + "OldObjectSample#" + Cutoff.NAME)); - long ticks = Utils.nanosToTicks(nanos); - JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples()); - } - } - - private void dumpMemoryToDestination(PlatformRecording recording, WriteableUserPath alternativePath) { - WriteableUserPath dest = alternativePath != null ? alternativePath : recording.getDestination(); + private void dumpMemoryToDestination(PlatformRecording recording) { + WriteableUserPath dest = recording.getDestination(); if (dest != null) { MetadataRepository.getInstance().setOutput(dest.getText()); recording.clearDestination(); } } - private void disableEvents() { MetadataRepository.getInstance().disableEvents(); } @@ -504,47 +498,47 @@ return newRec; } - public synchronized Recording newSnapshot() { + public synchronized void fillWithRecordedData(PlatformRecording target, Boolean pathToGcRoots) { boolean running = false; boolean toDisk = false; + for (PlatformRecording r : recordings) { if (r.getState() == RecordingState.RUNNING) { running = true; + if (r.isToDisk()) { + toDisk = true; + } } - if (r.isToDisk()) { - toDisk = true; - } - } // If needed, flush data from memory if (running) { if (toDisk) { + OldObjectSample.emit(recordings, pathToGcRoots); rotateDisk(); } else { - try (Recording snapshot = new Recording()) { - PlatformRecording internal = PrivateAccess.getInstance().getPlatformRecording(snapshot); - internal.setShouldWriteActiveRecordingEvent(false); + try (PlatformRecording snapshot = newTemporaryRecording()) { + snapshot.setToDisk(true); + snapshot.setShouldWriteActiveRecordingEvent(false); snapshot.start(); - snapshot.stop(); - return makeRecordingWithDiskChunks(); + OldObjectSample.emit(recordings, pathToGcRoots); + snapshot.stop("Snapshot dump"); + fillWithDiskChunks(target); } + return; } } - return makeRecordingWithDiskChunks(); + fillWithDiskChunks(target); } - private Recording makeRecordingWithDiskChunks() { - Recording snapshot = new Recording(); - snapshot.setName("Snapshot"); - PlatformRecording internal = PrivateAccess.getInstance().getPlatformRecording(snapshot); + private void fillWithDiskChunks(PlatformRecording target) { for (RepositoryChunk c : makeChunkList(null, null)) { - internal.add(c); + target.add(c); } - internal.setState(RecordingState.STOPPED); + target.setState(RecordingState.STOPPED); Instant startTime = null; Instant endTime = null; - for (RepositoryChunk c : makeChunkList(null, null)) { + for (RepositoryChunk c : target.getChunks()) { if (startTime == null || c.getStartTime().isBefore(startTime)) { startTime = c.getStartTime(); } @@ -559,9 +553,8 @@ if (endTime == null) { endTime = now; } - internal.setStartTime(startTime); - internal.setStopTime(endTime); - internal.setInternalDuration(Duration.between(startTime, endTime)); - return snapshot; + target.setStartTime(startTime); + target.setStopTime(endTime); + target.setInternalDuration(Duration.between(startTime, endTime)); } } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java --- 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 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 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 oldSettings = getSettings(); - Map 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 ordered = new TreeMap<>(settings); Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "New settings for recording \"" + getName() + "\" (" + getId() + ")"); for (Map.Entry 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 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 removeBefore(Instant time, List input) { + if (time == null) { + return input; + } + List result = new ArrayList<>(input.size()); + for (RepositoryChunk r : input) { + if (!r.getEndTime().isBefore(time)) { + result.add(r); + } + } + return result; + } + + private static List removeAfter(Instant time, List input) { + if (time == null) { + return input; + } + List result = new ArrayList<>(input.size()); + for (RepositoryChunk r : input) { + if (!r.getStartTime().isAfter(time)) { + result.add(r); + } + } + return result; + } + + private static List reduceFromBeginning(Long maxSize, List input) { + if (maxSize == null || input.isEmpty()) { + return input; + } + List 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 reduceFromEnd(Long maxSize, List input) { + Collections.reverse(input); + List result = reduceFromBeginning(maxSize, input); + Collections.reverse(result); + return result; + } + + public void setDumpOnExitDirectory(SafePath directory) { + this.dumpOnExitDirectory = directory; + } + + public SafePath getDumpOnExitDirectory() { + return this.dumpOnExitDirectory; + } } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java Mon Jun 25 02:07:42 2018 +0200 @@ -92,4 +92,6 @@ public abstract void setAnnotations(SettingDescriptor s, List a); public abstract boolean isUnsigned(ValueDescriptor v); + + public abstract PlatformRecorder getPlatformRecorder(); } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java Mon Jun 25 02:07:42 2018 +0200 @@ -41,7 +41,7 @@ private static final JVM jvm = JVM.getJVM(); private static final Repository instance = new Repository(); - static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter + public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter .ofPattern("yyyy_MM_dd_HH_mm_ss"); private final Set cleanupDirectories = new HashSet<>(); diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java Mon Jun 25 02:07:42 2018 +0200 @@ -26,12 +26,10 @@ package jdk.jfr.internal; import java.io.IOException; -import java.nio.file.Paths; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import java.time.LocalDateTime; import jdk.jfr.RecordingState; @@ -67,9 +65,10 @@ WriteableUserPath dest = recording.getDestination(); if (dest == null) { dest = makeDumpOnExitPath(recording); + recording.setDestination(dest); } if (dest != null) { - recording.stop("Dump on exit", dest); + recording.stop("Dump on exit"); } } catch (Exception e) { Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Could not dump recording " + recording.getName() + " on exit."); @@ -78,23 +77,21 @@ private WriteableUserPath makeDumpOnExitPath(PlatformRecording recording) { try { - String pid = JVM.getJVM().getPid(); - String dfText = Repository.REPO_DATE_FORMAT.format(LocalDateTime.now()); - String name = "hotspot-" + "pid-" + pid + "-id-" + recording.getId() + "-" + dfText + ".jfr"; + String name = Utils.makeFilename(recording.getRecording()); AccessControlContext acc = recording.getNoDestinationDumpOnExitAccessControlContext(); return AccessController.doPrivileged(new PrivilegedExceptionAction() { @Override public WriteableUserPath run() throws Exception { - return new WriteableUserPath(Paths.get(".", name)); + return new WriteableUserPath(recording.getDumpOnExitDirectory().toPath().resolve(name)); } }, acc); } catch (PrivilegedActionException e) { Throwable t = e.getCause(); if (t instanceof SecurityException) { - Logger.log(LogTag.JFR, LogLevel.WARN, "Not allowed to create dump path for recording " + recording.getId() + " on exit. " + e.getMessage()); + Logger.log(LogTag.JFR, LogLevel.WARN, "Not allowed to create dump path for recording " + recording.getId() + " on exit."); } if (t instanceof IOException) { - Logger.log(LogTag.JFR, LogLevel.WARN, "Could not dump " + recording.getId() + " on exit. " + e.getMessage()); + Logger.log(LogTag.JFR, LogLevel.WARN, "Could not dump " + recording.getId() + " on exit."); } return null; } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java Mon Jun 25 02:07:42 2018 +0200 @@ -43,6 +43,7 @@ import java.lang.reflect.Modifier; import java.nio.file.Path; import java.time.Duration; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -55,6 +56,7 @@ import jdk.internal.org.objectweb.asm.util.CheckClassAdapter; import jdk.jfr.Event; import jdk.jfr.FlightRecorderPermission; +import jdk.jfr.Recording; import jdk.jfr.RecordingState; import jdk.jfr.internal.handlers.EventHandler; import jdk.jfr.internal.settings.PeriodSetting; @@ -484,13 +486,6 @@ return Collections.unmodifiableList(list); } - public static void updateSettingPathToGcRoots(Map settings, Boolean pathToGcRoots) { - if (pathToGcRoots != null) { - settings.put(Type.EVENT_NAME_PREFIX + "OldObjectSample#cutoff", pathToGcRoots ? "infinity" : "0 ns" ); - } - } - - public static String upgradeLegacyJDKEvent(String eventName) { if (eventName.length() <= LEGACY_EVENT_NAME_PREFIX.length()) { return eventName; @@ -503,4 +498,11 @@ } return eventName; } + + public static String makeFilename(Recording recording) { + String pid = JVM.getJVM().getPid(); + String date = Repository.REPO_DATE_FORMAT.format(LocalDateTime.now()); + String idText = recording == null ? "" : "-id-" + Long.toString(recording.getId()); + return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr"; + } } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java Mon Jun 25 02:07:42 2018 +0200 @@ -27,8 +27,10 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.nio.file.Paths; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -63,15 +65,19 @@ return result.toString(); } - protected final SafePath resolvePath(String path, String errorMsg) throws DCmdException { - if (path == null) { - return null; + protected final SafePath resolvePath(Recording recording, String filename) throws InvalidPathException { + if (filename == null) { + return makeGenerated(recording, Paths.get(".")); } - try { - return new SafePath(path); - } catch (InvalidPathException e) { - throw new DCmdException(e, errorMsg, ", invalid path \"" + path + "\"."); + Path path = Paths.get(filename); + if (Files.isDirectory(path)) { + return makeGenerated(recording, path); } + return new SafePath(path.toAbsolutePath().normalize()); + } + + private SafePath makeGenerated(Recording recording, Path directory) { + return new SafePath(directory.toAbsolutePath().resolve(Utils.makeFilename(recording)).normalize()); } protected final Recording findRecording(String name) throws DCmdException { @@ -83,10 +89,12 @@ } } - protected final void reportOperationComplete(String actionPrefix, Recording r, SafePath file) { + protected final void reportOperationComplete(String actionPrefix, String name, SafePath file) { print(actionPrefix); - print(" recording "); - print("\"" + r.getName() + "\""); + print(" recording"); + if (name != null) { + print(" \"" + name + "\""); + } if (file != null) { print(","); try { @@ -136,7 +144,7 @@ } protected final void printBytes(long bytes, String separation) { - print(Utils.formatBytes(bytes, separation)); + print(Utils.formatBytes(bytes, separation)); } protected final void printTimespan(Duration timespan, String separator) { diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdCheck.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdCheck.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdCheck.java Mon Jun 25 02:07:42 2018 +0200 @@ -36,6 +36,9 @@ import jdk.jfr.EventType; import jdk.jfr.Recording; import jdk.jfr.SettingDescriptor; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; /** * JFR.check - invoked from native @@ -59,13 +62,17 @@ return getResult(); } - private void executeInternal(String recordingText, Boolean verbose) throws DCmdException { + private void executeInternal(String name, Boolean verbose) throws DCmdException { + if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdCheck: name=" + name + ", verbose=" + verbose); + } + if (verbose == null) { verbose = Boolean.FALSE; } - if (recordingText != null) { - printRecording(findRecording(recordingText), verbose); + if (name != null) { + printRecording(findRecording(name), verbose); return; } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java Mon Jun 25 02:07:42 2018 +0200 @@ -70,6 +70,19 @@ Boolean sampleThreads ) throws DCmdException { + if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdConfigure: repositorypath=" + repositoryPath + + ", dumppath=" + dumpPath + + ", stackdepth=" + stackDepth + + ", globalbuffercount=" + globalBufferCount + + ", globalbuffersize=" + globalBufferSize + + ", thread_buffer_size" + threadBufferSize + + ", memorysize" + memorySize + + ", maxchunksize=" + maxChunkSize + + ", samplethreads" + sampleThreads); + } + + boolean updated = false; if (repositoryPath != null) { try { diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdDump.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdDump.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdDump.java Mon Jun 25 02:07:42 2018 +0200 @@ -26,10 +26,21 @@ import java.io.IOException; import java.nio.file.InvalidPathException; -import java.util.HashMap; -import java.util.Map; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import jdk.jfr.FlightRecorder; import jdk.jfr.Recording; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; +import jdk.jfr.internal.PlatformRecorder; import jdk.jfr.internal.PlatformRecording; import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.SecuritySupport.SafePath; @@ -40,41 +51,155 @@ * JFR.dump * */ -//Instantiated by native +// Instantiated by native final class DCmdDump extends AbstractDCmd { /** * Execute JFR.dump. * - * @param recordingText name or id of the recording to dump, or - * null + * @param name name or id of the recording to dump, or null to dump everything * - * @param textPath file path where recording should be written. + * @param filename file path where recording should be written, not null + * @param maxAge how far back in time to dump, may be null + * @param maxSize how far back in size to dump data from, may be null + * @param begin point in time to dump data from, may be null + * @param end point in time to dump data to, may be null + * @param pathToGcRoots if Java heap should be swept for reference chains * * @return result output * * @throws DCmdException if the dump could not be completed */ - public String execute(String recordingText, String textPath, Boolean pathToGcRoots) throws DCmdException { - if (textPath == null) { - throw new DCmdException("Failed to dump %s, missing filename.", recordingText); + public String execute(String name, String filename, Long maxAge, Long maxSize, String begin, String end, Boolean pathToGcRoots) throws DCmdException { + if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, + "Executing DCmdDump: name=" + name + + ", filename=" + filename + + ", maxage=" + maxAge + + ", maxsize=" + maxSize + + ", begin=" + begin + + ", end" + end + + ", path-to-gc-roots=" + pathToGcRoots); + } + + if (FlightRecorder.getFlightRecorder().getRecordings().isEmpty()) { + throw new DCmdException("No recordings to dump from. Use JFR.start to start a recording."); + } + + if (maxAge != null) { + if (end != null || begin != null) { + throw new DCmdException("Dump failed, maxage can't be combined with begin or end."); + } + + if (maxAge < 0) { + throw new DCmdException("Dump failed, maxage can't be negative."); + } + if (maxAge == 0) { + maxAge = Long.MAX_VALUE / 2; // a high value that won't overflow + } } - Recording recording = findRecording(recordingText); - try { - SafePath dumpFile = resolvePath(textPath, "Failed to dump %s"); - // create file for JVM - Utils.touch(dumpFile.toPath()); - PlatformRecording r = PrivateAccess.getInstance().getPlatformRecording(recording); - WriteableUserPath wup = new WriteableUserPath(dumpFile.toPath()); + + if (maxSize!= null) { + if (maxSize < 0) { + throw new DCmdException("Dump failed, maxsize can't be negative."); + } + if (maxSize == 0) { + maxSize = Long.MAX_VALUE / 2; // a high value that won't overflow + } + } + + Instant beginTime = parseTime(begin, "begin"); + Instant endTime = parseTime(end, "end"); - Map overlay = new HashMap<>(); - Utils.updateSettingPathToGcRoots(overlay, pathToGcRoots); + if (beginTime != null && endTime != null) { + if (endTime.isBefore(beginTime)) { + throw new DCmdException("Dump failed, begin must preceed end."); + } + } - r.copyTo(wup, "Dumped by user", overlay); - reportOperationComplete("Dumped", recording, dumpFile); - } catch (IOException | InvalidPathException e) { - throw new DCmdException("Failed to dump %s. Could not copy recording for dump. %s", recordingText, e.getMessage()); + Duration duration = null; + if (maxAge != null) { + duration = Duration.ofNanos(maxAge); + beginTime = Instant.now().minus(duration); + } + Recording recording = null; + if (name != null) { + recording = findRecording(name); + } + PlatformRecorder recorder = PrivateAccess.getInstance().getPlatformRecorder(); + synchronized (recorder) { + dump(recorder, recording, name, filename, maxSize, pathToGcRoots, beginTime, endTime); } return getResult(); } + public void dump(PlatformRecorder recorder, Recording recording, String name, String filename, Long maxSize, Boolean pathToGcRoots, Instant beginTime, Instant endTime) throws DCmdException { + try (PlatformRecording r = newSnapShot(recorder, recording, pathToGcRoots)) { + r.filter(beginTime, endTime, maxSize); + if (r.getChunks().isEmpty()) { + throw new DCmdException("Dump failed. No data found in the specified interval."); + } + SafePath dumpFile = resolvePath(recording, filename); + + // Needed for JVM + Utils.touch(dumpFile.toPath()); + r.dumpStopped(new WriteableUserPath(dumpFile.toPath())); + reportOperationComplete("Dumped", name, dumpFile); + } catch (IOException | InvalidPathException e) { + throw new DCmdException("Dump failed. Could not copy recording data. %s", e.getMessage()); + } + } + + private Instant parseTime(String time, String parameter) throws DCmdException { + if (time == null) { + return null; + } + try { + return Instant.parse(time); + } catch (DateTimeParseException dtp) { + // fall through + } + try { + LocalDateTime ldt = LocalDateTime.parse(time); + return ZonedDateTime.of(ldt, ZoneId.systemDefault()).toInstant(); + } catch (DateTimeParseException dtp) { + // fall through + } + try { + LocalTime lt = LocalTime.parse(time); + LocalDate ld = LocalDate.now(); + Instant instant = ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant(); + Instant now = Instant.now(); + if (instant.isAfter(now) && !instant.isBefore(now.plusSeconds(3600))) { + // User must have meant previous day + ld = ld.minusDays(1); + } + return ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant(); + } catch (DateTimeParseException dtp) { + // fall through + } + + if (time.startsWith("-")) { + try { + long durationNanos = Utils.parseTimespan(time.substring(1)); + Duration duration = Duration.ofNanos(durationNanos); + return Instant.now().minus(duration); + } catch (NumberFormatException nfe) { + // fall through + } + } + throw new DCmdException("Dump failed, not a valid %s time.", parameter); + } + + private PlatformRecording newSnapShot(PlatformRecorder recorder, Recording recording, Boolean pathToGcRoots) throws DCmdException, IOException { + if (recording == null) { + // Operate on all recordings + PlatformRecording snapshot = recorder.newTemporaryRecording(); + recorder.fillWithRecordedData(snapshot, pathToGcRoots); + return snapshot; + } + + PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording); + return pr.newSnapshotClone("Dumped by user", pathToGcRoots); + } + } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java Mon Jun 25 02:07:42 2018 +0200 @@ -25,19 +25,26 @@ package jdk.jfr.internal.dcmd; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.InvalidPathException; +import java.nio.file.Path; import java.nio.file.Paths; import java.text.ParseException; import java.time.Duration; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import jdk.jfr.FlightRecorder; import jdk.jfr.Recording; import jdk.jfr.internal.JVM; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; +import jdk.jfr.internal.OldObjectSample; +import jdk.jfr.internal.PrivateAccess; import jdk.jfr.internal.SecuritySupport.SafePath; import jdk.jfr.internal.Type; -import jdk.jfr.internal.Utils; import jdk.jfr.internal.jfc.JFC; /** @@ -51,7 +58,7 @@ * Execute JFR.start. * * @param name optional name that can be used to identify recording. - * @param configurations names of settings files to use, i.e. "default" or + * @param settings names of settings files to use, i.e. "default" or * "default.jfc". * @param delay delay before recording is started, in nanoseconds. Must be * at least 1 second. @@ -73,7 +80,19 @@ * @throws DCmdException if recording could not be started */ @SuppressWarnings("resource") - public String execute(String name, String[] configurations, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException { + public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException { + if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name + + ", settings=" + Arrays.asList(settings) + + ", delay=" + delay + + ", duration=" + duration + + ", disk=" + disk+ + ", filename=" + path + + ", maxage=" + maxAge + + ", maxsize=" + maxSize + + ", dumponexit =" + dumpOnExit + + ", path-to-gc-roots=" + pathToGcRoots); + } if (name != null) { try { Integer.parseInt(name); @@ -86,25 +105,23 @@ if (duration == null && Boolean.FALSE.equals(dumpOnExit) && path != null) { throw new DCmdException("Filename can only be set for a time bound recording or if dumponexit=true. Set duration/dumponexit or omit filename."); } - if (dumpOnExit == null && path != null) { - dumpOnExit = Boolean.TRUE; - } + Map s = new HashMap<>(); - if (configurations == null || configurations.length == 0) { - configurations = new String[] { "default" }; + if (settings == null || settings.length == 0) { + settings = new String[] { "default" }; } - for (String configName : configurations) { + for (String configName : settings) { try { s.putAll(JFC.createKnown(configName).getSettings()); } catch (IOException | ParseException e) { - throw new DCmdException("Could not parse setting " + configurations[0], e); + throw new DCmdException("Could not parse setting " + settings[0], e); } } - Utils.updateSettingPathToGcRoots(s, pathToGcRoots); + OldObjectSample.updateSettingPathToGcRoots(s, pathToGcRoots); if (duration != null) { if (duration < 1000L * 1000L * 1000L) { @@ -133,10 +150,24 @@ recording.setToDisk(disk.booleanValue()); } recording.setSettings(s); + SafePath safePath = null; if (path != null) { try { - recording.setDestination(Paths.get(path)); + if (dumpOnExit == null) { + // default to dumponexit=true if user specified filename + dumpOnExit = Boolean.TRUE; + } + Path p = Paths.get(path); + if (Files.isDirectory(p) && Boolean.TRUE.equals(dumpOnExit)) { + // Decide destination filename at dump time + // Purposely avoid generating filename in Recording#setDestination due to + // security concerns + PrivateAccess.getInstance().getPlatformRecording(recording).setDumpOnExitDirectory(new SafePath(p)); + } else { + safePath = resolvePath(recording, path); + recording.setDestination(safePath.toPath()); + } } catch (IOException | InvalidPathException e) { recording.close(); throw new DCmdException("Could not start recording, not able to write to file %s. %s ", path, e.getMessage()); @@ -175,10 +206,10 @@ recording.setMaxSize(250*1024L*1024L); } - if (path != null && duration != null) { + if (safePath != null && duration != null) { println(" The result will be written to:"); println(); - printPath(new SafePath(path)); + printPath(safePath); } else { println(); println(); diff -r 6c306d54366d -r a390cbb82d47 src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStop.java --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStop.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStop.java Mon Jun 25 02:07:42 2018 +0200 @@ -29,13 +29,16 @@ import java.nio.file.Paths; import jdk.jfr.Recording; +import jdk.jfr.internal.LogLevel; +import jdk.jfr.internal.LogTag; +import jdk.jfr.internal.Logger; import jdk.jfr.internal.SecuritySupport.SafePath; /** * JFR.stop * */ -//Instantiated by native +// Instantiated by native final class DCmdStop extends AbstractDCmd { /** @@ -43,35 +46,42 @@ * * Requires that either name or id is set. * - * @param recordingText name or id of the recording to stop. + * @param name name or id of the recording to stop. * - * @param textPath file path where data should be written after recording - * has been stopped, or null if recording shouldn't be - * written to disk. + * @param filename file path where data should be written after recording has + * been stopped, or null if recording shouldn't be written + * to disk. * @return result text * * @throws DCmdException if recording could not be stopped */ - public String execute(String recordingText, String textPath) throws DCmdException { + public String execute(String name, String filename) throws DCmdException { + if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) { + Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name + ", filename=" + filename); + } + try { - SafePath path = resolvePath(textPath, "Failed to stop %s"); - Recording recording = findRecording(recordingText); - if (textPath != null) { + SafePath safePath = null; + Recording recording = findRecording(name); + if (filename != null) { try { - recording.setDestination(Paths.get(textPath)); - } catch (IOException e) { - throw new DCmdException("Failed to stop %s. Could not set destination for \"%s\" to file %s", recording.getName(), textPath, e.getMessage()); + // Ensure path is valid. Don't generate safePath if filename == null, as a user may + // want to stop recording without a dump + safePath = resolvePath(null, filename); + recording.setDestination(Paths.get(filename)); + } catch (IOException | InvalidPathException e) { + throw new DCmdException("Failed to stop %s. Could not set destination for \"%s\" to file %s", recording.getName(), filename, e.getMessage()); } } recording.stop(); - reportOperationComplete("Stopped", recording, path); + reportOperationComplete("Stopped", recording.getName(), safePath); recording.close(); return getResult(); } catch (InvalidPathException | DCmdException e) { - if (textPath != null) { - throw new DCmdException("Could not write recording \"%s\" to file. %s", recordingText, e.getMessage()); + if (filename != null) { + throw new DCmdException("Could not write recording \"%s\" to file. %s", name, e.getMessage()); } - throw new DCmdException(e, "Could not stop recording \"%s\".", recordingText, e.getMessage()); + throw new DCmdException(e, "Could not stop recording \"%s\".", name, e.getMessage()); } } } diff -r 6c306d54366d -r a390cbb82d47 src/jdk.management.jfr/share/classes/jdk/management/jfr/MBeanUtils.java --- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/MBeanUtils.java Sun Jun 24 16:25:47 2018 +0100 +++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/MBeanUtils.java Mon Jun 25 02:07:42 2018 +0200 @@ -122,7 +122,7 @@ } int size = Integer.parseInt(string); if (size <1) { - throw new IllegalArgumentException("Block size msut be at least 1 byte"); + throw new IllegalArgumentException("Block size must be at least 1 byte"); } return size; } diff -r 6c306d54366d -r a390cbb82d47 test/jdk/jdk/jfr/jcmd/JcmdAsserts.java --- a/test/jdk/jdk/jfr/jcmd/JcmdAsserts.java Sun Jun 24 16:25:47 2018 +0100 +++ b/test/jdk/jdk/jfr/jcmd/JcmdAsserts.java Mon Jun 25 02:07:42 2018 +0200 @@ -47,7 +47,7 @@ output.shouldMatch("Flight Recorder has been used"); } - public static void assertRecordingDumpedToFile(OutputAnalyzer output, String name, File recording) { + public static void assertRecordingDumpedToFile(OutputAnalyzer output, File recording) { output.shouldContain("Dumped recording"); output.shouldContain(recording.getAbsolutePath()); } diff -r 6c306d54366d -r a390cbb82d47 test/jdk/jdk/jfr/jcmd/TestJcmdDump.java --- a/test/jdk/jdk/jfr/jcmd/TestJcmdDump.java Sun Jun 24 16:25:47 2018 +0100 +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDump.java Mon Jun 25 02:07:42 2018 +0200 @@ -26,8 +26,16 @@ package jdk.jfr.jcmd; import java.io.File; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; -import jdk.test.lib.jfr.FileHelper; +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.jfr.consumer.RecordingFile; +import jdk.test.lib.jfr.EventNames; import jdk.test.lib.process.OutputAnalyzer; /* @@ -35,24 +43,129 @@ * @summary The test verifies JFR.dump command * @key jfr * @library /test/lib /test/jdk - * @run main/othervm jdk.jfr.jcmd.TestJcmdDump + * @run main/othervm -XX:FlightRecorderOptions:maxchunksize=1M jdk.jfr.jcmd.TestJcmdDump */ public class TestJcmdDump { + static class StoppedEvent extends Event { + } + static class RunningEvent extends Event { + } + + private static final String[] names = { null, "r1" }; + private static final boolean booleanValues[] = { true, false }; + public static void main(String[] args) throws Exception { - String name = "TestJcmdDump"; - File recording = new File(name + ".jfr"); + + // Create a stopped recording in the repository to complicate things + Recording r = new Recording(); + r.start(); + StoppedEvent de = new StoppedEvent(); + de.commit(); + r.stop(); + // The implementation of JFR.dump touch code that can't be executed using the + // Java API. It is therefore important to try all combinations. The + // implementation is non-trivial and depends on the combination + for (String name : names) { + for (boolean disk : booleanValues) { + try (Recording r1 = new Recording(); Recording r2 = new Recording()) { + System.out.println(); + System.out.println(); + System.out.println("Starting recordings with disk=" + disk); + r1.setToDisk(disk); + // To complicate things, only enable OldObjectSample for one recording + r1.enable(EventNames.OldObjectSample).withoutStackTrace(); + r1.setName("r1"); + r2.setToDisk(disk); + r2.setName("r2"); + r1.start(); + r2.start(); + + // Expect no path to GC roots + jfrDump(Boolean.FALSE, name, disk, rootCount -> rootCount == 0); + // Expect path to GC roots + jfrDump(null, name, disk, rootCount -> rootCount == 0); + // Expect at least one path to a GC root + jfrDump(Boolean.TRUE, name, disk, rootCount -> rootCount > 0); + } + } + } + r.close(); // release recording data from the stopped recording + } - OutputAnalyzer output = JcmdHelper.jcmd("JFR.start", "name=" + name); - JcmdAsserts.assertRecordingHasStarted(output); - JcmdHelper.waitUntilRunning(name); + private static void jfrDump(Boolean pathToGCRoots, String name, boolean disk, Predicate successPredicate) throws Exception { + List leakList = new ArrayList<>(); + leakList.add(new Object[1000_0000]); + System.gc(); + while (true) { + RunningEvent re = new RunningEvent(); + re.commit(); + leakList.add(new Object[1000_0000]); + leakList.add(new Object[1000_0000]); + leakList.add(new Object[1000_0000]); + System.gc(); // This will shorten time for object to be emitted. + File recording = new File("TestJCMdDump.jfr"); + String[] params = buildParameters(pathToGCRoots, name, recording); + OutputAnalyzer output = JcmdHelper.jcmd(params); + JcmdAsserts.assertRecordingDumpedToFile(output, recording); + int rootCount = 0; + int oldObjectCount = 0; + int stoppedEventCount = 0; + int runningEventCount = 0; + for (RecordedEvent e : RecordingFile.readAllEvents(recording.toPath())) { + if (e.getEventType().getName().equals(EventNames.OldObjectSample)) { + if (e.getValue("root") != null) { + rootCount++; + } + oldObjectCount++; + } + if (e.getEventType().getName().equals(StoppedEvent.class.getName())) { + stoppedEventCount++; + } + if (e.getEventType().getName().equals(RunningEvent.class.getName())) { + runningEventCount++; + } + } + System.out.println("Name: " + name); + System.out.println("Disk: " + disk); + System.out.println("Path to GC roots: " + pathToGCRoots); + System.out.println("Old Objects: " + oldObjectCount); + System.out.println("Root objects: "+ rootCount); + System.out.println("Stopped events: "+ stoppedEventCount); + System.out.println("Running events: "+ runningEventCount); - output = JcmdHelper.jcmd("JFR.dump", - "name=" + name, - "filename=" + recording.getAbsolutePath()); - JcmdAsserts.assertRecordingDumpedToFile(output, name, recording); - JcmdHelper.stopAndCheck(name); - FileHelper.verifyRecording(recording); + System.out.println(); + if (runningEventCount == 0) { + throw new Exception("Missing event from running recording"); + } + if (name == null && stoppedEventCount == 0) { + throw new Exception("Missing event from stopped recording"); + } + if (name != null && stoppedEventCount > 0) { + throw new Exception("Stopped event should not be part of dump"); + } + if (oldObjectCount != 0 && successPredicate.test(rootCount)) { + return; + } + System.out.println(); + System.out.println(); + System.out.println(); + System.out.println("************* Retrying! **************"); + Files.delete(recording.toPath()); + } + } + + private static String[] buildParameters(Boolean pathToGCRoots, String name, File recording) { + List params = new ArrayList<>(); + params.add("JFR.dump"); + params.add("filename=" + recording.getAbsolutePath()); + if (pathToGCRoots != null) { // if path-to-gc-roots is omitted, default is used (disabled). + params.add("path-to-gc-roots=" + pathToGCRoots); + } + if (name != null) { // if name is omitted, all recordings will be dumped + params.add("name=" + name); + } + return params.toArray(new String[0]); } } diff -r 6c306d54366d -r a390cbb82d47 test/jdk/jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java Mon Jun 25 02:07:42 2018 +0200 @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2015, 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.jcmd; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; + +import jdk.jfr.Configuration; +import jdk.jfr.Recording; +import jdk.test.lib.jfr.FileHelper; +import jdk.test.lib.process.OutputAnalyzer; + +/* + * @test + * @summary The test verifies JFR.dump command + * @key jfr + * @library /test/lib /test/jdk + * @run main/othervm jdk.jfr.jcmd.TestJcmdDumpGeneratedFilename + */ +public class TestJcmdDumpGeneratedFilename { + + public static void main(String[] args) throws Exception { + // Increase the id for a recording + for (int i = 0; i < 300; i++) { + new Recording(); + } + try (Recording r = new Recording(Configuration.getConfiguration("default"))) { + r.start(); + r.stop(); + testDumpFilename(); + testDumpFilename(r); + testDumpDiectory(); + testDumpDiectory(r); + } + } + + private static void testDumpFilename() throws Exception { + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump"); + verifyFile(readFilename(output), null); + } + + private static void testDumpFilename(Recording r) throws Exception { + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "name=" + r.getId()); + verifyFile(readFilename(output), r.getId()); + } + + private static void testDumpDiectory() throws Exception { + Path directory = Paths.get(".").toAbsolutePath().normalize(); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + directory); + String filename = readFilename(output); + verifyFile(filename, null); + verifyDirectory(filename, directory); + } + + private static void testDumpDiectory(Recording r) throws Exception { + Path directory = Paths.get(".").toAbsolutePath().normalize(); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "name=" + r.getId(), "filename=" + directory); + String filename = readFilename(output); + verifyFile(filename, r.getId()); + verifyDirectory(filename, directory); + } + + private static void verifyDirectory(String filename, Path directory) throws Exception { + if (!filename.contains(directory.toAbsolutePath().normalize().toString())) { + throw new Exception("Expected dump to be at " + directory); + } + } + + private static void verifyFile(String filename, Long id) throws Exception { + String idText = id == null ? "" : "-id-" + Long.toString(id); + String expectedName = "hotspot-pid-" + ProcessHandle.current().pid() + idText; + if (!filename.contains(expectedName)) { + throw new Exception("Expected filename to contain " + expectedName); + } + FileHelper.verifyRecording(new File(filename)); + } + + private static String readFilename(OutputAnalyzer output) throws Exception { + Iterator it = output.asLines().iterator(); + while (it.hasNext()) { + String line = it.next(); + if (line.contains("written to")) { + line = it.next(); // blank line + return it.next(); + } + } + throw new Exception("Could not find filename of dumped recording."); + } +} diff -r 6c306d54366d -r a390cbb82d47 test/jdk/jdk/jfr/jcmd/TestJcmdDumpLimited.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpLimited.java Mon Jun 25 02:07:42 2018 +0200 @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2015, 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.jcmd; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import jdk.jfr.Event; +import jdk.jfr.Recording; +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; + +/* + * @test + * @summary The test verifies JFR.dump command + * @key jfr + * @library /test/lib /test/jdk + * @run main/othervm -XX:FlightRecorderOptions jdk.jfr.jcmd.TestJcmdDumpLimited + */ +public class TestJcmdDumpLimited { + + static class TestEvent extends Event { + int id; + int number; + } + + static class TestRecording { + Instant time; + final Recording r; + Path path; + int size; + int total; + int id; + Instant now; + + TestRecording(int id, int events) throws IOException, InterruptedException { + r = new Recording(); + r.start(); + for (int i = 0; i < events; i++) { + TestEvent event = new TestEvent(); + event.id = id; + event.number = i; + event.commit(); + if (i == events / 2) { + time = Instant.now(); + } + } + r.stop(); + Thread.sleep(1); + path = Paths.get("dump-" + id + ".jfr"); + r.dump(path); + size = (int) Files.size(path); + this.id = id; + this.now = Instant.now(); + } + + public void close() { + r.close(); + } + } + + private static long totalSize; + private static long lastFiveSize; + private static long firstFiveSize; + private static long middleSize; + private static long centerSize; + private static long lastSize; + + private static Instant middle; + private static Instant centerLeft; + private static Instant centerRight; + + public static void main(String[] args) throws Exception { + + List recs = new ArrayList<>(); + + for (int i = 0; i < 9; i++) { + recs.add(new TestRecording(i, 100)); + } + int last = 0; + List reversed = new ArrayList<>(recs); + Collections.reverse(reversed); + for (TestRecording r : reversed) { + r.total = r.size + last; + last += r.size; + } + + for (TestRecording r : recs) { + System.out.println("Recording " + r.id + ": size=" + r.size + " (total=" + r.total + ", time=" + r.now + ")"); + } + + centerLeft = recs.get(3).time; + middle = recs.get(4).time; + centerRight = recs.get(5).time; + + totalSize = size(recs, 0, 9); + lastFiveSize = size(recs, 4, 5); + firstFiveSize = size(recs, 0, 5); + middleSize = size(recs, 4, 1); + centerSize = size(recs, 3, 3); + lastSize = size(recs, 8, 1); + + testDump(); + testDumpMaxSize(); + testDumpMaxSizeSmall(); + testDumpBegin(); + testDumpEnd(); + testDumpBeginEndInstant(); + testDumpBeginEndLocalDateTime(); + testDumpBeginEndLocalTime(); + testDumpBeginEndSame(); + testDumpMaxAge(); + testDumpBeginEndRelative(); + testDumpTooEarly(); + testDumpTooLate(); + testDumpBeginMaxAge(); + TestDumpEndMaxage(); + testDumpEndBegin(); + testDumpInvalidTime(); + } + + private static int size(List recs, int skip, int limit) { + return recs.stream().skip(skip).limit(limit).mapToInt(r -> r.size).sum(); + } + + private static void testDumpEndBegin() throws Exception { + Path testEndBegin = Paths.get("testEndBegin.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + testEndBegin.toFile().getAbsolutePath(), "begin=" + Instant.now(), "end=" + Instant.now().minusSeconds(200)); + output.shouldContain("Dump failed, begin must preceed end."); + assertMissingFile(testEndBegin); + } + + private static void TestDumpEndMaxage() throws Exception { + Path testEndMaxAge = Paths.get("testEndMaxAge.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + testEndMaxAge.toFile().getAbsolutePath(), "end=" + Instant.now(), "maxage=2h"); + output.shouldContain("Dump failed, maxage can't be combined with begin or end."); + assertMissingFile(testEndMaxAge); + } + + private static Path testDumpBeginMaxAge() throws Exception { + Path testBeginMaxAge = Paths.get("testBeginMaxAge.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginMaxAge.toFile().getAbsolutePath(), "begin=" + Instant.now().minusSeconds(100), "maxage=2h"); + output.shouldContain("Dump failed, maxage can't be combined with begin or end."); + assertMissingFile(testBeginMaxAge); + return testBeginMaxAge; + } + + private static void testDumpTooLate() throws Exception { + Path missing = Paths.get("missing2.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + missing.toFile().getAbsolutePath(), "begin=" + Instant.now().plus(Duration.ofHours(1)), + "end=" + Instant.now().plus(Duration.ofHours(2))); + output.shouldContain("Dump failed. No data found in the specified interval."); + assertMissingFile(missing); + } + + private static void testDumpTooEarly() throws Exception { + Path missing = Paths.get("missing.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + missing.toFile().getAbsolutePath(), "end=" + Instant.now().minus(Duration.ofHours(1))); + output.shouldContain("Dump failed. No data found in the specified interval."); + assertMissingFile(missing); + } + + private static void testDumpBeginEndRelative() throws IOException { + Path testBeginEndRelative = Paths.get("testBeginEndRelative.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEndRelative.toFile().getAbsolutePath(), "begin=-3h", "end=-0s"); + Asserts.assertEquals(totalSize, Files.size(testBeginEndRelative), "Expected dump with begin=-3h end=0s to contain data from all recordings"); + Files.delete(testBeginEndRelative); + } + + private static void testDumpMaxAge() throws IOException { + Path testMaxAge = Paths.get("testMaxAge.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testMaxAge.toFile().getAbsolutePath(), "maxage=2h"); + Asserts.assertEquals(totalSize, Files.size(testMaxAge), "Expected dump with maxage=2h to contain data from all recordings"); + Files.delete(testMaxAge); + } + + private static void testDumpBeginEndSame() throws IOException { + Path testBeginEnd = Paths.get("testBeginEndSame.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + middle, "end=" + middle); + Asserts.assertEquals(middleSize, Files.size(testBeginEnd), "Expected dump with begin=" + middle + "end=" + middle + " contain data from middle recording"); + Files.delete(testBeginEnd); + } + + private static void testDumpBeginEndInstant() throws IOException { + Path testBeginEnd = Paths.get("testBeginEndInstant.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + centerLeft, "end=" + centerRight); + Asserts.assertEquals(centerSize, Files.size(testBeginEnd), "Expected dump with begin=" + centerLeft + " end=" + centerRight + " contain data from the 'center'-recordings"); + Files.delete(testBeginEnd); + } + + private static void testDumpBeginEndLocalDateTime() throws IOException { + LocalDateTime centerLeftLocal = LocalDateTime.ofInstant(centerLeft, ZoneOffset.systemDefault()); + LocalDateTime centerRightLocal = LocalDateTime.ofInstant(centerRight, ZoneOffset.systemDefault()); + Path testBeginEnd = Paths.get("testBeginEndLocalDateTime.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + centerLeftLocal, "end=" + centerRightLocal); + Asserts.assertEquals(centerSize, Files.size(testBeginEnd), "Expected dump with begin=" + centerLeftLocal + " end=" + centerRightLocal + " contain data from the 'center'-recordings"); + Files.delete(testBeginEnd); + } + + private static void testDumpBeginEndLocalTime() throws IOException { + LocalTime centerLeftLocal = LocalTime.ofInstant(centerLeft, ZoneOffset.systemDefault()); + LocalTime centerRightLocal = LocalTime.ofInstant(centerRight, ZoneOffset.systemDefault()); + Path testBeginEnd = Paths.get("testBeginEndLocalTime.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + centerLeftLocal, "end=" + centerRightLocal); + Asserts.assertEquals(centerSize, Files.size(testBeginEnd), "Expected dump with begin=" + centerLeftLocal + " end=" + centerRightLocal + " contain data from the 'center'-recordings"); + Files.delete(testBeginEnd); + } + + private static void testDumpEnd() throws IOException { + Path testEnd = Paths.get("testEnd.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testEnd.toFile().getAbsolutePath(), "end=" + middle); + Asserts.assertEquals(firstFiveSize, Files.size(testEnd), "Expected dump with end=" + middle + " to contain data from the five first recordings"); + Files.delete(testEnd); + } + + private static void testDumpBegin() throws IOException { + Path testBegin = Paths.get("testBegin.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testBegin.toFile().getAbsolutePath(), "begin=" + middle); + Asserts.assertEquals(lastFiveSize, Files.size(testBegin), "Expected dump with begin=" + middle + " to contain data from the last five recordings"); + Files.delete(testBegin); + } + + private static void testDumpMaxSize() throws IOException { + Path testMaxSize = Paths.get("testMaxSize.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testMaxSize.toFile().getAbsolutePath(), "maxsize=" + lastFiveSize); + Asserts.assertEquals(lastFiveSize, Files.size(testMaxSize), "Expected dump with maxsize=" + lastFiveSize + " to contain data from the last five recordings"); + Files.delete(testMaxSize); + } + + private static void testDumpMaxSizeSmall() throws IOException { + Path testMaxSizeSmall = Paths.get("testMaxSizeSmall.jfr"); + JcmdHelper.jcmd("JFR.dump", "filename=" + testMaxSizeSmall.toFile().getAbsolutePath(), "maxsize=1k"); + Asserts.assertEquals(lastSize, Files.size(testMaxSizeSmall), "Expected dump with maxsize=1k to contain data from the last recording"); + Files.delete(testMaxSizeSmall); + } + + private static void testDump() throws IOException { + Path all = Paths.get("all.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + all.toFile().getAbsolutePath()); + JcmdAsserts.assertRecordingDumpedToFile(output, all.toFile()); + Asserts.assertEquals(totalSize, Files.size(all), "Expected dump to be sum of all recordings"); + Files.delete(all); + } + + private static void testDumpInvalidTime() throws Exception { + Path invalidTime = Paths.get("invalidTime.jfr"); + OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + invalidTime.toFile().getAbsolutePath(), "begin=4711"); + output.shouldContain("Dump failed, not a valid begin time."); + assertMissingFile(invalidTime); + } + + private static void assertMissingFile(Path missing) throws Exception { + if (Files.exists(missing)) { + throw new Exception("Unexpected dumpfile found"); + } + } + +} diff -r 6c306d54366d -r a390cbb82d47 test/jdk/jdk/jfr/jcmd/TestJcmdLegacy.java --- a/test/jdk/jdk/jfr/jcmd/TestJcmdLegacy.java Sun Jun 24 16:25:47 2018 +0100 +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdLegacy.java Mon Jun 25 02:07:42 2018 +0200 @@ -28,6 +28,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; @@ -58,13 +59,13 @@ private static void testJcmd() throws Exception { String name = "testLegacy"; - File p = new File(name + ".jfr"); + Path p = Paths.get(name + ".jfr").toAbsolutePath().normalize(); OutputAnalyzer output = JcmdHelper.jcmd("JFR.start", "name=" + name, "settings=" + SETTINGS.getCanonicalPath()); JcmdAsserts.assertRecordingHasStarted(output); JcmdHelper.waitUntilRunning(name); - JcmdHelper.stopWriteToFileAndCheck(name, p); - FileHelper.verifyRecording(p); - verify(p.toPath()); + JcmdHelper.stopWriteToFileAndCheck(name, p.toFile()); + FileHelper.verifyRecording(p.toFile()); + verify(p); } private static void testAPI() throws IOException, Exception { diff -r 6c306d54366d -r a390cbb82d47 test/jdk/jdk/jfr/jcmd/TestJcmdStartStopDefault.java --- a/test/jdk/jdk/jfr/jcmd/TestJcmdStartStopDefault.java Sun Jun 24 16:25:47 2018 +0100 +++ b/test/jdk/jdk/jfr/jcmd/TestJcmdStartStopDefault.java Mon Jun 25 02:07:42 2018 +0200 @@ -25,7 +25,8 @@ package jdk.jfr.jcmd; -import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,7 +44,7 @@ public class TestJcmdStartStopDefault { public static void main(String[] args) throws Exception { - File recording = new File("TestJcmdStartStopDefault.jfr"); + Path recording = Paths.get(".","TestJcmdStartStopDefault.jfr").toAbsolutePath().normalize(); OutputAnalyzer output = JcmdHelper.jcmd("JFR.start"); JcmdAsserts.assertRecordingHasStarted(output); @@ -53,10 +54,10 @@ output = JcmdHelper.jcmd("JFR.dump", "name=" + name, - "filename=" + recording.getAbsolutePath()); - JcmdAsserts.assertRecordingDumpedToFile(output, name, recording); + "filename=" + recording); + JcmdAsserts.assertRecordingDumpedToFile(output, recording.toFile()); JcmdHelper.stopAndCheck(name); - FileHelper.verifyRecording(recording); + FileHelper.verifyRecording(recording.toFile()); } private static String parseRecordingName(OutputAnalyzer output) {