--- 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);
--- 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<char*> _name;
DCmdArgument<char*> _filename;
+ DCmdArgument<NanoTimeArgument> _maxage;
+ DCmdArgument<MemorySizeArgument> _maxsize;
+ DCmdArgument<char*> _begin;
+ DCmdArgument<char*> _end;
DCmdArgument<bool> _path_to_gc_roots;
public:
--- 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
--- 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)
--- 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<Recording> getRecordings() {
List<Recording> 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<String, String> settings) {
- return internal.newRecording(settings);
+ PlatformRecorder getInternal() {
+ return internal;
}
-
}
--- 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();
+ }
}
/**
--- 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<String, String> 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<String, String>()));
- }
+ this(new HashMap<String, String>());
+ }
/**
* 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));
+
}
/**
--- 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);
+ }
}
--- /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<PlatformRecording> 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<String, String> s, Boolean pathToGcRoots) {
+ if (pathToGcRoots != null) {
+ s.put(OLD_OBJECT_CUTOFF, pathToGcRoots ? "infinity" : "0 ns");
+ }
+ }
+
+ public static Map<String, String> createSettingsForSnapshot(PlatformRecording recording, Boolean pathToGcRoots) {
+ Map<String, String> settings = new HashMap<>(recording.getSettings());
+ updateSettingPathToGcRoots(settings, pathToGcRoots);
+ return settings;
+ }
+
+ private static boolean isEnabled(PlatformRecording r) {
+ Map<String, String> settings = r.getSettings();
+ String s = settings.get(OLD_OBJECT_ENABLED);
+ return "true".equals(s);
+ }
+}
--- 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<String, String> 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<String, String> 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<String, String> 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));
}
}
--- 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;
+ }
}
--- 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<AnnotationElement> a);
public abstract boolean isUnsigned(ValueDescriptor v);
+
+ public abstract PlatformRecorder getPlatformRecorder();
}
--- 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<SafePath> cleanupDirectories = new HashSet<>();
--- 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<WriteableUserPath>() {
@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;
}
--- 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<String, String> 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";
+ }
}
--- 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) {
--- 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;
}
--- 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 {
--- 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
- * <code>null</code>
+ * @param name name or id of the recording to dump, or <code>null</code> 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<String, String> 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);
+ }
+
}
--- 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<String, String> 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();
--- 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 <code>name or <code>id</code> 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 <code>null</code> if recording shouldn't be
- * written to disk.
+ * @param filename file path where data should be written after recording has
+ * been stopped, or <code>null</code> 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());
}
}
}
--- 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;
}
--- 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());
}
--- 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<Integer> successPredicate) throws Exception {
+ List<Object> 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<String> 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]);
}
}
--- /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<String> 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.");
+ }
+}
--- /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<TestRecording> recs = new ArrayList<>();
+
+ for (int i = 0; i < 9; i++) {
+ recs.add(new TestRecording(i, 100));
+ }
+ int last = 0;
+ List<TestRecording> 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<TestRecording> 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");
+ }
+ }
+
+}
--- 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 {
--- 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) {