address comments from PR#1
This commit is contained in:
Родитель
f0d67a5c99
Коммит
38942cea08
|
@ -0,0 +1,12 @@
|
|||
This project welcomes contributions and suggestions. Most contributions require you to
|
||||
agree to a Contributor License Agreement (CLA) declaring that you have the right to,
|
||||
and actually do, grant us the rights to use your contribution. For details, visit
|
||||
https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need
|
||||
to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
|
||||
instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
15
README.md
15
README.md
|
@ -43,18 +43,3 @@ The build is vanilla Maven.
|
|||
`mvn compile` - compile the source code
|
||||
`mvn test` - run unit tests (this project uses TestNG)
|
||||
`mvn package` - build the .jar file
|
||||
|
||||
# Contribute
|
||||
|
||||
This project welcomes contributions and suggestions. Most contributions require you to
|
||||
agree to a Contributor License Agreement (CLA) declaring that you have the right to,
|
||||
and actually do, grant us the rights to use your contribution. For details, visit
|
||||
https://cla.microsoft.com.
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need
|
||||
to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the
|
||||
instructions provided by the bot. You will only need to do this once across all repositories using our CLA.
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
|
||||
or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
|
|
@ -60,12 +60,12 @@ public class FlightRecorderConnection {
|
|||
* @return A {@code FlightRecorderConnection}.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws InstanceNotFoundException The FlightRecorder MBean is not registered on the target JVM.
|
||||
* @throws java.lang.InternalError Wraps a {@code javax.management.MalformedObjectNameException}
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.MalformedObjectNameException}
|
||||
* and indicates a bug in this class.
|
||||
* @throws NullPointerException The {@code mBeanServerConnection} parameter is {@code null}.
|
||||
*/
|
||||
public static FlightRecorderConnection connect(MBeanServerConnection mBeanServerConnection)
|
||||
throws IOException, InstanceNotFoundException
|
||||
throws IOException, InstanceNotFoundException, JfrStreamingException
|
||||
{
|
||||
Objects.requireNonNull(mBeanServerConnection);
|
||||
try {
|
||||
|
@ -75,7 +75,7 @@ public class FlightRecorderConnection {
|
|||
} catch (MalformedObjectNameException e) {
|
||||
// Not expected to happen. This exception comes from the ObjectName constructor. If
|
||||
// JFR_OBJECT_NAME is malformed, then this is an internal bug.
|
||||
throw new InternalError(JFR_OBJECT_NAME, e);
|
||||
throw new JfrStreamingException(JFR_OBJECT_NAME, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,32 +102,26 @@ public class FlightRecorderConnection {
|
|||
* the {@link #newRecording(RecordingOptions, RecordingConfiguration)} method
|
||||
* @return The id of the recording.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws java.lang.InternalError Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException}
|
||||
* and indicates an issue with the FlightRecorderMXBean in the JVM.
|
||||
* The cause may also be a {@code javax.management.openmbean.OpenDataException}
|
||||
* which indicates a bug in the code of this class.
|
||||
*/
|
||||
long startRecording(RecordingOptions recordingOptions, RecordingConfiguration recordingConfiguration)
|
||||
throws IOException {
|
||||
/* package-scoped */ long startRecording(RecordingOptions recordingOptions, RecordingConfiguration recordingConfiguration)
|
||||
throws IOException, JfrStreamingException {
|
||||
|
||||
try {
|
||||
final long id = (long) connection.invoke(
|
||||
flightRecorder,
|
||||
"newRecording",
|
||||
new Object[]{},
|
||||
new String[]{}
|
||||
);
|
||||
Object[] args = new Object[]{};
|
||||
String[] argTypes = new String[]{};
|
||||
final long id = (long) connection.invoke(flightRecorder, "newRecording", args, argTypes);
|
||||
|
||||
if (recordingConfiguration != null) {
|
||||
String predefinedConfiguration = recordingConfiguration.getName();
|
||||
if (predefinedConfiguration != null && predefinedConfiguration.trim().length() > 0) {
|
||||
connection.invoke(
|
||||
flightRecorder,
|
||||
"setPredefinedConfiguration",
|
||||
new Object[]{id, predefinedConfiguration},
|
||||
new String[]{long.class.getName(), String.class.getName()}
|
||||
);
|
||||
args = new Object[]{id, predefinedConfiguration};
|
||||
argTypes = new String[]{long.class.getName(), String.class.getName()};
|
||||
connection.invoke(flightRecorder, "setPredefinedConfiguration", args, argTypes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,26 +129,20 @@ public class FlightRecorderConnection {
|
|||
Map<String,String> options = recordingOptions.getRecordingOptions();
|
||||
if (options != null && !options.isEmpty()) {
|
||||
TabularData recordingOptionsParam = makeOpenData(options);
|
||||
connection.invoke(
|
||||
flightRecorder,
|
||||
"setRecordingOptions",
|
||||
new Object[]{id, recordingOptionsParam},
|
||||
new String[]{long.class.getName(), TabularData.class.getName()}
|
||||
);
|
||||
args = new Object[]{id, recordingOptionsParam};
|
||||
argTypes = new String[]{long.class.getName(), TabularData.class.getName()};
|
||||
connection.invoke(flightRecorder, "setRecordingOptions", args, argTypes);
|
||||
}
|
||||
}
|
||||
|
||||
connection.invoke(
|
||||
flightRecorder,
|
||||
"startRecording",
|
||||
new Object[]{id},
|
||||
new String[]{long.class.getName()}
|
||||
);
|
||||
args = new Object[]{id};
|
||||
argTypes = new String[]{long.class.getName()};
|
||||
connection.invoke(flightRecorder, "startRecording", args, argTypes);
|
||||
|
||||
return id;
|
||||
} catch (OpenDataException|InstanceNotFoundException|MBeanException|ReflectionException e) {
|
||||
// In theory, we should never get these.
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
throw new JfrStreamingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,15 +150,17 @@ public class FlightRecorderConnection {
|
|||
* Stop a recording. This method is called from the {@link Recording#stop()} method.
|
||||
* @param id The id of the recording.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws java.lang.InternalError Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException}
|
||||
* and indicates an issue with the FlightRecorderMXBean in the JVM.
|
||||
*/
|
||||
void stopRecording(long id) throws IOException {
|
||||
/* package-scoped */ void stopRecording(long id) throws IOException, JfrStreamingException {
|
||||
try {
|
||||
connection.invoke(flightRecorder, "stopRecording", new Object[]{id}, new String[]{long.class.getName()});
|
||||
Object[] args = new Object[]{id};
|
||||
String[] argTypes = new String[]{long.class.getName()};
|
||||
connection.invoke(flightRecorder, "stopRecording", args, argTypes);
|
||||
} catch (InstanceNotFoundException|MBeanException|ReflectionException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
throw new JfrStreamingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,13 +170,15 @@ public class FlightRecorderConnection {
|
|||
* @param id The id of the recording.
|
||||
* @param outputFile the system-dependent file name where data is written, not {@code null}
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
*/
|
||||
void dumpRecording(long id, String outputFile) throws IOException {
|
||||
/* package-scoped */ void dumpRecording(long id, String outputFile) throws IOException, JfrStreamingException {
|
||||
try {
|
||||
connection.invoke(flightRecorder, "copyTo", new Object[]{id,outputFile}, new String[]{long.class.getName(),String.class.getName()});
|
||||
Object[] args = new Object[]{id,outputFile};
|
||||
String[] argTypes = new String[]{long.class.getName(),String.class.getName()};
|
||||
connection.invoke(flightRecorder, "copyTo", args, argTypes);
|
||||
} catch (InstanceNotFoundException|MBeanException|ReflectionException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
throw new JfrStreamingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,13 +189,15 @@ public class FlightRecorderConnection {
|
|||
* @param id The id of the recording being cloned.
|
||||
* @param stop Whether to stop the cloned recording.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
*/
|
||||
long cloneRecording(long id, boolean stop) throws IOException {
|
||||
/* package-scoped */ long cloneRecording(long id, boolean stop) throws IOException, JfrStreamingException {
|
||||
try {
|
||||
return (long)connection.invoke(flightRecorder, "cloneRecording", new Object[]{id,stop}, new String[]{long.class.getName(),boolean.class.getName()});
|
||||
Object[] args = new Object[]{id,stop};
|
||||
String[] argTypes = new String[]{long.class.getName(),boolean.class.getName()};
|
||||
return (long)connection.invoke(flightRecorder, "cloneRecording", args, argTypes);
|
||||
} catch (InstanceNotFoundException|MBeanException|ReflectionException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
throw new JfrStreamingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,14 +221,14 @@ public class FlightRecorderConnection {
|
|||
* @param blockSize The number of bytes to read at a time.
|
||||
* @return A {@code InputStream} of the Java Flight Recording data.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws InternalError Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException}
|
||||
* and indicates an issue with the FlightRecorderMXBean in the JVM.
|
||||
* The cause may also be a {@code javax.management.openmbean.OpenDataException}
|
||||
* which indicates a bug in the code of this class.
|
||||
*/
|
||||
InputStream getStream(long id, Instant startTime, Instant endTime, long blockSize)
|
||||
throws IOException {
|
||||
/* package-scoped */ InputStream getStream(long id, Instant startTime, Instant endTime, long blockSize)
|
||||
throws IOException, JfrStreamingException {
|
||||
Map<String,String> options = new HashMap<>();
|
||||
if (startTime != null) options.put("startTime", startTime.toString());
|
||||
if (endTime != null) options.put("endTime", endTime.toString());
|
||||
|
@ -242,10 +236,12 @@ public class FlightRecorderConnection {
|
|||
|
||||
try {
|
||||
TabularData streamOptions = makeOpenData(options);
|
||||
long streamId = (long) connection.invoke(flightRecorder, "openStream", new Object[]{id, streamOptions}, new String[]{long.class.getName(), TabularData.class.getName()});
|
||||
Object[] args = new Object[]{id, streamOptions};
|
||||
String[] argTypes = new String[]{long.class.getName(), TabularData.class.getName()};
|
||||
long streamId = (long) connection.invoke(flightRecorder, "openStream", args, argTypes);
|
||||
return new JfrStream(connection, flightRecorder, streamId);
|
||||
} catch(OpenDataException|InstanceNotFoundException|MBeanException|ReflectionException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
throw new JfrStreamingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,15 +249,17 @@ public class FlightRecorderConnection {
|
|||
* Close the recording. This method is called from the {@link Recording#close()} method.
|
||||
* @param id The id of the recording.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws java.lang.InternalError Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* @throws JfrStreamingException Wraps an {@code javax.management.InstanceNotFoundException},
|
||||
* a {@code javax.management.MBeanException} or a {@code javax.management.ReflectionException}
|
||||
* and indicates an issue with the FlightRecorderMXBean in the JVM.
|
||||
*/
|
||||
void closeRecording(long id) throws IOException {
|
||||
/* package-scoped */ void closeRecording(long id) throws IOException, JfrStreamingException {
|
||||
try {
|
||||
connection.invoke(flightRecorder, "closeRecording", new Object[]{id}, new String[]{long.class.getName()});
|
||||
Object[] args = new Object[]{id};
|
||||
String[] argTypes = new String[]{long.class.getName()};
|
||||
connection.invoke(flightRecorder, "closeRecording", args, argTypes);
|
||||
} catch (InstanceNotFoundException|MBeanException|ReflectionException e) {
|
||||
throw new InternalError(e.getMessage(), e);
|
||||
throw new JfrStreamingException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ import javax.management.ReflectionException;
|
|||
*/
|
||||
class JfrStream extends InputStream {
|
||||
|
||||
|
||||
/* A default value for blockSize used by FlightRecorderMXBean#readStream(long) */
|
||||
private static final long DEFAULT_BLOCKSIZE = Long.getLong("jfr.stream.blocksize", 50000L);
|
||||
|
||||
|
@ -29,6 +28,8 @@ class JfrStream extends InputStream {
|
|||
private byte[] buffer;
|
||||
private int index = 0;
|
||||
private boolean EOF = false;
|
||||
// There is a recording id and an id you get from the recording for the stream.
|
||||
// streamId is the id for the stream.
|
||||
private final long streamid;
|
||||
private final MBeanServerConnection connection;
|
||||
private final ObjectName flightRecorder;
|
||||
|
|
|
@ -3,12 +3,11 @@ package com.microsoft.jfr;
|
|||
import javax.management.JMException;
|
||||
|
||||
/**
|
||||
* An InternalError is a wrapper around specific {@code javax.management.JMException}
|
||||
* An JfrStreamingException is a wrapper around specific {@code javax.management.JMException}
|
||||
* instances which might be thrown, either directly or indirectly, by methods of this package.
|
||||
* Exceptions of this type are not expected from a well behaved JVM. This class is
|
||||
* a subclass of {@code java.lang.Error} and may be treated as an unchecked exception.
|
||||
* Exceptions of this type are not expected from a well behaved JVM.
|
||||
*
|
||||
* The cause of an {@code InternalError} will be one of the following:
|
||||
* The cause of an {@code JfrStreamingException} will be one of the following:
|
||||
* <dl>
|
||||
* <dt><em>javax.management.InstanceNotFoundException</em></dt>
|
||||
* <dd>The FlightRecorderMXBean is not found on the MBean server. This could happen
|
||||
|
@ -32,9 +31,9 @@ import javax.management.JMException;
|
|||
* were not met. This cause indicates a bug in the com.microsoft.censum.jfr package code.</dd>
|
||||
* </dl>
|
||||
*/
|
||||
public class InternalError extends Error {
|
||||
public class JfrStreamingException extends Exception {
|
||||
|
||||
InternalError(String message, JMException cause) {
|
||||
JfrStreamingException(String message, JMException cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
|
@ -20,7 +20,15 @@ public class Recording implements AutoCloseable {
|
|||
|
||||
/**
|
||||
* A {@code Recording} may be in one of these states. Note that a {@code Recording} is
|
||||
* no longer usable once it is in the {@code CLOSED} state.
|
||||
* no longer usable once it is in the {@code CLOSED} state. Valid state transitions are
|
||||
* <code>
|
||||
* NEW -> [RECORDING, STOPPED, CLOSED]
|
||||
* RECORDING -> [RECORDING, STOPPED, CLOSED]
|
||||
* STOPPED -> [RECORDING, STOPPED, CLOSED]
|
||||
* CLOSED -> [CLOSED]
|
||||
* </code>
|
||||
* Calling a method on {@code Recording} that would cause an invalid transition
|
||||
* will raise an IllegalStateException.
|
||||
*/
|
||||
public enum State {
|
||||
/**
|
||||
|
@ -43,7 +51,17 @@ public class Recording implements AutoCloseable {
|
|||
}
|
||||
|
||||
// Format for IllegalStateException that this class might throw
|
||||
// {0} is the state the code is trying to transition to.
|
||||
// {1} are the states that the instance could be in for a valid transition.
|
||||
private final static MessageFormat illegalStateFormat = new MessageFormat("Recording state {0} not in [{1}]");
|
||||
|
||||
/**
|
||||
* Helper for formatting the message for an IllegalStateException that may be thrown by methods of this class.
|
||||
* @param actual This is the state that the Recording is in currently
|
||||
* @param expected This is the state that the Recording should be in for a valid transition to occur
|
||||
* @param others Additional <em>expected</em> states
|
||||
* @return
|
||||
*/
|
||||
private static String createIllegalStateExceptionMessage(State actual, State expected, State... others) {
|
||||
String[] args = new String[]{actual.name(), expected.name()};
|
||||
if (others != null) {
|
||||
|
@ -67,7 +85,7 @@ public class Recording implements AutoCloseable {
|
|||
* @param recordingOptions The options to be used for the recording
|
||||
* @param recordingConfiguration The settings for events to be collected by the recording
|
||||
*/
|
||||
/*package*/ Recording(
|
||||
/* package-scoped */ Recording(
|
||||
FlightRecorderConnection connection,
|
||||
RecordingOptions recordingOptions,
|
||||
RecordingConfiguration recordingConfiguration) {
|
||||
|
@ -81,7 +99,7 @@ public class Recording implements AutoCloseable {
|
|||
* Get the recording id. The recording does not have an id until the recording is started.
|
||||
* @return The recording id, or {@code -1} if the recording was never started.
|
||||
*/
|
||||
public long getId() throws IllegalStateException {
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
@ -89,11 +107,11 @@ public class Recording implements AutoCloseable {
|
|||
* Start a recording. A recording may not be started after it is closed.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws IllegalStateException This {@code Recording} is closed.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
* @return The recording id.
|
||||
*/
|
||||
public long start() throws IOException, IllegalStateException {
|
||||
// state machine transitions: NEW -> RECORDING or STOPPED -> RECORDING, otherwise remain in state
|
||||
public long start() throws IOException, IllegalStateException, JfrStreamingException {
|
||||
// state transitions: NEW -> RECORDING or STOPPED -> RECORDING, otherwise remain in state
|
||||
State oldState = state.getAndUpdate(s -> s == State.NEW || s == State.STOPPED ? State.RECORDING : s);
|
||||
|
||||
if (oldState == State.NEW || oldState == State.STOPPED) {
|
||||
|
@ -108,10 +126,10 @@ public class Recording implements AutoCloseable {
|
|||
* Stop a recording.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws IllegalStateException If the {@code Recording} is closed.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
*/
|
||||
public void stop() throws IOException, IllegalStateException {
|
||||
// state machine transitions: RECORDING -> STOPPED, otherwise remain in state
|
||||
public void stop() throws IOException, IllegalStateException, JfrStreamingException {
|
||||
// state transitions: RECORDING -> STOPPED, otherwise remain in state
|
||||
State oldState = state.getAndUpdate(s -> s == State.RECORDING ? State.STOPPED : s);
|
||||
if (oldState == State.RECORDING) {
|
||||
connection.stopRecording(id);
|
||||
|
@ -126,10 +144,10 @@ public class Recording implements AutoCloseable {
|
|||
* @param outputFile the system-dependent file name where data is written, not {@code null}
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws IllegalStateException If the {@code Recording} has not been started, or has been closed.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
* @throws NullPointerException If the {@code outputFile} argument is null.
|
||||
*/
|
||||
public void dump(String outputFile) throws IOException, IllegalStateException {
|
||||
public void dump(String outputFile) throws IOException, IllegalStateException, JfrStreamingException {
|
||||
Objects.requireNonNull(outputFile, "outputFile may not be null");
|
||||
State currentState = state.get();
|
||||
if (currentState == State.RECORDING || currentState == State.STOPPED) {
|
||||
|
@ -147,9 +165,9 @@ public class Recording implements AutoCloseable {
|
|||
* @return The cloned recording.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws IllegalStateException If the {@code Recording} has not been started, or has been closed.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
*/
|
||||
public Recording clone(boolean stop) throws IOException {
|
||||
public Recording clone(boolean stop) throws IOException, JfrStreamingException {
|
||||
State currentState = state.get();
|
||||
if (currentState == State.RECORDING || currentState == State.STOPPED) {
|
||||
long newId = connection.cloneRecording(id, stop);
|
||||
|
@ -170,10 +188,11 @@ public class Recording implements AutoCloseable {
|
|||
* @return An {@code InputStream}, or {@code null} if no data is available in the interval.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws IllegalStateException If the {@code Recording} has not been stopped.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
* @see JfrStream#getDefaultBlockSize()
|
||||
*/
|
||||
public InputStream getStream(Instant startTime, Instant endTime) throws IOException, IllegalStateException {
|
||||
public InputStream getStream(Instant startTime, Instant endTime)
|
||||
throws IOException, IllegalStateException, JfrStreamingException {
|
||||
return getStream(startTime, endTime, JfrStream.getDefaultBlockSize());
|
||||
}
|
||||
|
||||
|
@ -190,11 +209,11 @@ public class Recording implements AutoCloseable {
|
|||
* @return An {@code InputStream}, or {@code null} if no data is available in the interval.
|
||||
* @throws IOException A communication problem occurred when talking to the MBean server.
|
||||
* @throws IllegalStateException If the {@code Recording} has not been stopped.
|
||||
* @throws InternalError Wraps a {@code javax.management.JMException}. See {@link InternalError}.
|
||||
* @throws JfrStreamingException Wraps a {@code javax.management.JMException}. See {@link JfrStreamingException}.
|
||||
*/
|
||||
public InputStream getStream(Instant startTime, Instant endTime, long blockSize)
|
||||
throws IOException, IllegalStateException {
|
||||
// state machine transitions: remain in state
|
||||
throws IOException, IllegalStateException, JfrStreamingException {
|
||||
// state transitions: remain in state
|
||||
State currentState = state.get();
|
||||
if (currentState == State.STOPPED) {
|
||||
return connection.getStream(id, startTime, endTime, blockSize);
|
||||
|
@ -213,7 +232,7 @@ public class Recording implements AutoCloseable {
|
|||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// state machine transitions: any -> CLOSED
|
||||
// state transitions: any -> CLOSED
|
||||
State oldState = state.getAndSet(State.CLOSED);
|
||||
if (oldState == State.RECORDING) {
|
||||
try {
|
||||
|
|
|
@ -18,6 +18,22 @@ import java.util.stream.Stream;
|
|||
* {@link FlightRecorderConnection#newRecording(RecordingOptions, RecordingConfiguration)}. A {@code Configuration}
|
||||
* is immutable which prevents attempts at changing the configuration while a recording
|
||||
* is in progress.
|
||||
*
|
||||
* <b>A note on the API</b>
|
||||
* It is enticing to want the Builder to take a {@code java.time.Duration} instead of a String for
|
||||
* the {@code maxAge} or {@code duration} API, or have the {@code maxAge} API take a long, or pass
|
||||
* a boolean for the others. The problem with this is twofold. First, it makes it difficult for the user of
|
||||
* the library to set values through system properties since the String value has to be converted. This is
|
||||
* not really an issue for a long or boolean arg, but it adds a lot of code for handling a Duration. And
|
||||
* it makes it difficult to document what value a user should provide for a system property. These are not
|
||||
* insurmountable problems. But they do add error-prone complexity. Secondly, the arguments to
|
||||
* FlightRecorderMXBean are String, so there would need to be a conversion from the Duration/boolean/long to
|
||||
* a String anyway. And then what API would you have for Recording? If Recording#getDuration returns a
|
||||
* Duration and this is called from the underlying code, then the underlying code has to do the conversion
|
||||
* and this creates a tight coupling between Recording and the underlying code. If Builder takes a Duration
|
||||
* and Recording returns a String, then the two APIs are not parallel (violates the rule of least surprise).
|
||||
* Sticking to a String based API resolves these issues. But it does mean that the Builder needs to validate
|
||||
* the args and potentially throw IllegalArgumentException. String makes the overall code so much simpler.
|
||||
*/
|
||||
public class RecordingOptions {
|
||||
|
||||
|
@ -35,7 +51,7 @@ public class RecordingOptions {
|
|||
DISK("disk", "false"),
|
||||
DURATION("duration", NO_LIMIT);
|
||||
|
||||
private Option(String name, String defaultValue) {
|
||||
Option(String name, String defaultValue) {
|
||||
this.name = name;
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
@ -43,22 +59,20 @@ public class RecordingOptions {
|
|||
private final String defaultValue; /* not null! */
|
||||
}
|
||||
|
||||
/* If the arg is null or an empty String, return the Option's default. */
|
||||
private static String normalize(String arg, Option option) {
|
||||
return arg == null || (arg = arg.trim()).isEmpty() ? option.defaultValue : arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link RecordingOptions}.
|
||||
* Builder for {@link RecordingOptions}. The builder builds up a hash table of options.
|
||||
* These options correspond to the recording options for a
|
||||
* {@code jdk.management.jfr.FlightRecorderMXBean}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
// The builder builds up a hash table of options. These options
|
||||
// correspond to the recording options for a FlightRecorderMXBean
|
||||
// Even though the input to the builder methods are not all String,
|
||||
// the option value is converted to a String. This simplifies
|
||||
// the code in the RecordingOption constructor, but makes the
|
||||
// RecordingOption getter methods a bit strange since they have to
|
||||
// covert from the String back to the raw value. The getter methods
|
||||
// could return String, but then you have the builder taking one type
|
||||
// and the getter returning a different type, which would be surprising.
|
||||
private final Map<Option, String> options = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* Constructor for a {@code Builder}.
|
||||
*/
|
||||
|
@ -75,7 +89,7 @@ public class RecordingOptions {
|
|||
public Builder name(String name) {
|
||||
options.put(
|
||||
Option.NAME,
|
||||
name != null ? name.trim() : Option.NAME.defaultValue
|
||||
normalize(name,Option.NAME)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
@ -125,7 +139,7 @@ public class RecordingOptions {
|
|||
public Builder maxSize(String maxSize) throws IllegalArgumentException {
|
||||
long value = 0L;
|
||||
try {
|
||||
String numVal = maxSize == null || maxSize.trim().length() == 0 ? Option.MAX_SIZE.defaultValue : maxSize.trim();
|
||||
String numVal = normalize(maxSize, Option.MAX_SIZE);
|
||||
value = Long.parseLong(numVal);
|
||||
if (value < 0L) {
|
||||
throw new IllegalArgumentException("maxSize: " + value + " < 0");
|
||||
|
@ -169,9 +183,7 @@ public class RecordingOptions {
|
|||
public Builder destination(String destination) {
|
||||
options.put(
|
||||
Option.DESTINATION,
|
||||
destination != null
|
||||
? destination.trim()
|
||||
: Option.DESTINATION.defaultValue
|
||||
normalize(destination,Option.DESTINATION)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
@ -186,7 +198,7 @@ public class RecordingOptions {
|
|||
public Builder disk(String disk) {
|
||||
options.put(
|
||||
Option.DISK,
|
||||
Boolean.valueOf(disk).toString()
|
||||
Boolean.valueOf(normalize(disk, Option.DISK)).toString()
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
@ -239,8 +251,10 @@ public class RecordingOptions {
|
|||
* @param builder The builder that was used to parameterize the configuration
|
||||
*/
|
||||
private RecordingOptions(Builder builder) {
|
||||
|
||||
// Note we're converting from Map<Option<?>,String> to Map<String,String>
|
||||
final Map<Option,String> options = builder.options;
|
||||
|
||||
//
|
||||
// For each option,
|
||||
// if the option is not the default value
|
||||
|
@ -330,7 +344,7 @@ public class RecordingOptions {
|
|||
* according to FlightRecorderMXBean.
|
||||
* @return A read-only map of the recording options.
|
||||
*/
|
||||
/*package*/ Map<String,String> getRecordingOptions() {
|
||||
/* package-scoped */ Map<String,String> getRecordingOptions() {
|
||||
return recordingOptions;
|
||||
}
|
||||
|
||||
|
@ -340,30 +354,30 @@ public class RecordingOptions {
|
|||
private final Map<String,String> recordingOptions;
|
||||
|
||||
// format for FlightRecorderMXBean maxAge and duration recording options
|
||||
private static final Pattern pattern = Pattern.compile("([-+]?\\d+)\\s*(\\w*)");
|
||||
private static final Pattern durationPattern = Pattern.compile("([-+]?\\d+)\\s*(\\w*)");
|
||||
|
||||
// If the durationString arg is a valid format, return the arg properly formatted for FlightRecorderMXBean.
|
||||
// The expected format is a positive number, followed by a space (optional),
|
||||
// followed by the units (one of ns, us, ms, s, m, h, d).
|
||||
private static String validateDuration(Option option, String durationString) throws IllegalArgumentException {
|
||||
|
||||
if (durationString == null || durationString.trim().length() == 0) {
|
||||
if (durationString == null || durationString.trim().isEmpty()) {
|
||||
return option.defaultValue;
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.matcher(durationString);
|
||||
if (matcher.matches()) {
|
||||
Matcher durationStringMatcher = durationPattern.matcher(durationString);
|
||||
if (durationStringMatcher.matches()) {
|
||||
|
||||
final long value;
|
||||
try {
|
||||
value = Long.parseLong(matcher.group(1));
|
||||
value = Long.parseLong(durationStringMatcher.group(1));
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
if (value >= 0L) {
|
||||
|
||||
final String units = matcher.group(2);
|
||||
final String units = durationStringMatcher.group(2);
|
||||
switch (units) {
|
||||
case "":
|
||||
return Long.toString(value);
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
/**
|
||||
* This package provides API for controlling Java Flight Recordings (JFR) through Java Management Extensions (JMX).
|
||||
*
|
||||
* JDK 9 introduced the {@code jdk.jfr} API which is not available in JDK 8. The
|
||||
* {@code jdk.management.jfr.FlightRecorderMXBean} is available in Java 8 and higher.
|
||||
* By relying on JMX and the {@code jdk.management.jfr.FlightRecorderMXBean},
|
||||
* the {@code com.microsoft.jfr} package provides access to JFR on local or remote JVMs.
|
||||
*/
|
||||
package com.microsoft.jfr;
|
|
@ -1,6 +1,7 @@
|
|||
package com.microsoft.jfr;
|
||||
|
||||
import org.testng.annotations.DataProvider;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -19,14 +20,14 @@ public class RecordingOptionsTest {
|
|||
{null, ""}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "nameValues")
|
||||
@Test(dataProvider = "nameValues")
|
||||
public void testGetName(String[] args) {
|
||||
String expected = args[1];
|
||||
RecordingOptions opts = new RecordingOptions.Builder().name(args[0]).build();
|
||||
assertEquals(opts.getName(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetNameDefault() {
|
||||
String expected = "";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
|
@ -50,13 +51,13 @@ public class RecordingOptionsTest {
|
|||
{null, "0"}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "maxAgeGoodValues")
|
||||
@Test(dataProvider = "maxAgeGoodValues")
|
||||
public void testGetMaxAge(String[] args) {
|
||||
RecordingOptions opts = new RecordingOptions.Builder().maxAge(args[0]).build();
|
||||
assertEquals(opts.getMaxAge(), args[1]);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetMaxAgeDefault() {
|
||||
String expected = "0";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
|
@ -77,7 +78,7 @@ public class RecordingOptionsTest {
|
|||
{"3 _ ms"}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "maxAgeBadValues", expectedExceptions = {IllegalArgumentException.class})
|
||||
@Test(dataProvider = "maxAgeBadValues", expectedExceptions = {IllegalArgumentException.class})
|
||||
public void testGetMaxAgeNegative(String[] args) {
|
||||
RecordingOptions opts = new RecordingOptions.Builder().maxAge(args[0]).build();
|
||||
}
|
||||
|
@ -95,13 +96,13 @@ public class RecordingOptionsTest {
|
|||
{null, "0"}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "maxSizeGoodValues")
|
||||
@Test(dataProvider = "maxSizeGoodValues")
|
||||
public void testGetMaxSize(String[] args) {
|
||||
RecordingOptions opts = new RecordingOptions.Builder().maxSize(args[0]).build();
|
||||
assertEquals(opts.getMaxSize(), args[1]);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetMaxSizeDefault() {
|
||||
String expected = "0";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
|
@ -117,26 +118,26 @@ public class RecordingOptionsTest {
|
|||
{"0xBEEF"}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "maxSizeBadValues", expectedExceptions = {IllegalArgumentException.class})
|
||||
@Test(dataProvider = "maxSizeBadValues", expectedExceptions = {IllegalArgumentException.class})
|
||||
public void testGetMaxSizeNegative(String[] args) {
|
||||
RecordingOptions opts = new RecordingOptions.Builder().maxSize(args[0]).build();
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDumpOnExit() {
|
||||
String expected = "true";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().dumpOnExit(expected).build();
|
||||
assertEquals(opts.getDumpOnExit(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDumpOnExitDefault() {
|
||||
String expected = "false";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
assertEquals(opts.getDumpOnExit(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDumpOnExitBadValue() {
|
||||
String expected = "false";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().dumpOnExit("BAD_VALUE").build();
|
||||
|
@ -153,35 +154,35 @@ public class RecordingOptionsTest {
|
|||
{null, ""}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "destinationValues")
|
||||
@Test(dataProvider = "destinationValues")
|
||||
public void testGetDestination(String[] args) {
|
||||
String expected = args[1];
|
||||
RecordingOptions opts = new RecordingOptions.Builder().destination(args[0]).build();
|
||||
assertEquals(opts.getDestination(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDestinationDefault() {
|
||||
String expected = "";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
assertEquals(opts.getDestination(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDisk() {
|
||||
String expected = "true";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().disk(expected).build();
|
||||
assertEquals(opts.getDisk(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDiskDefault() {
|
||||
String expected = "false";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
assertEquals(opts.getDisk(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDiskBadValue() {
|
||||
String expected = "false";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().disk("BAD_VALUE").build();
|
||||
|
@ -205,14 +206,14 @@ public class RecordingOptionsTest {
|
|||
{null, "0"}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "durationGoodValues")
|
||||
@Test(dataProvider = "durationGoodValues")
|
||||
public void testGetDuration(String[] args) {
|
||||
String expected = args[1];
|
||||
RecordingOptions opts = new RecordingOptions.Builder().duration(args[0]).build();
|
||||
assertEquals(opts.getDuration(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetDurationDefault() {
|
||||
String expected = "0";
|
||||
RecordingOptions opts = new RecordingOptions.Builder().build();
|
||||
|
@ -233,12 +234,12 @@ public class RecordingOptionsTest {
|
|||
{"3 _ ms"}
|
||||
};
|
||||
}
|
||||
@org.testng.annotations.Test(dataProvider = "durationBadValues", expectedExceptions = {IllegalArgumentException.class})
|
||||
@Test(dataProvider = "durationBadValues", expectedExceptions = {IllegalArgumentException.class})
|
||||
public void testGetDurationNegative(String[] args) {
|
||||
RecordingOptions opts = new RecordingOptions.Builder().duration(args[0]).build();
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetRecordingOptions() {
|
||||
Map<String,String> expected = new HashMap<>();
|
||||
expected.put("name", "test");
|
||||
|
@ -260,7 +261,7 @@ public class RecordingOptionsTest {
|
|||
assertEquals(opts.getRecordingOptions(), expected);
|
||||
}
|
||||
|
||||
@org.testng.annotations.Test
|
||||
@Test
|
||||
public void testGetRecordingOptionsDefaults() {
|
||||
Map<String,String> expected = new HashMap<>();
|
||||
// Due to a bug, some JVMs default "disk=true". So include "disk=false" (the documented default)
|
||||
|
|
|
@ -39,7 +39,6 @@ import static org.testng.Assert.*;
|
|||
public class RecordingTest {
|
||||
|
||||
FlightRecorderConnection flightRecorderConnection = null;
|
||||
Set<Path> jfrFilesToSave = new HashSet<>();
|
||||
|
||||
@BeforeTest
|
||||
public void setup() {
|
||||
|
@ -51,6 +50,8 @@ public class RecordingTest {
|
|||
} catch (IOException e) {
|
||||
// possible that this can be thrown, but should not happen in this context
|
||||
fail("IOException not expected", e);
|
||||
} catch (JfrStreamingException reallyBad) {
|
||||
fail ("something really bad happened", reallyBad);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,7 +61,6 @@ public class RecordingTest {
|
|||
Path userDir = Paths.get(System.getProperty("user.dir"));
|
||||
Files.list(userDir)
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(path -> !jfrFilesToSave.contains(path))
|
||||
.filter(path -> path.toString().endsWith(".jfr"))
|
||||
.forEach(jfrFile -> {
|
||||
try {
|
||||
|
@ -76,7 +76,7 @@ public class RecordingTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testNewRecording() {
|
||||
public void assertNewRecordingInitialValues() {
|
||||
assert flightRecorderConnection != null;
|
||||
Recording recording = flightRecorderConnection.newRecording(null, null);
|
||||
assertEquals(recording.getState(), Recording.State.NEW);
|
||||
|
@ -85,20 +85,20 @@ public class RecordingTest {
|
|||
|
||||
|
||||
@Test
|
||||
public void testRecordingStart() {
|
||||
public void assertRecordingStartIdAndState() {
|
||||
assert flightRecorderConnection != null;
|
||||
Recording recording = flightRecorderConnection.newRecording(null, null);
|
||||
try {
|
||||
long id = recording.start();
|
||||
assertEquals(recording.getId(), id);
|
||||
assertEquals(recording.getState(), Recording.State.RECORDING);
|
||||
} catch (IOException|IllegalStateException| InternalError e) {
|
||||
fail("Recording start threw exception", e);
|
||||
} catch (IOException|IllegalStateException| JfrStreamingException e) {
|
||||
fail("assertRecordingStartIdAndState caught exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecordingStop() {
|
||||
public void assertRecordingStopState() {
|
||||
assert flightRecorderConnection != null;
|
||||
Recording recording = flightRecorderConnection.newRecording(null, null);
|
||||
try {
|
||||
|
@ -106,13 +106,13 @@ public class RecordingTest {
|
|||
assertEquals(recording.getId(), id);
|
||||
recording.stop();
|
||||
assertEquals(recording.getState(), Recording.State.STOPPED);
|
||||
} catch (IOException|IllegalStateException| InternalError e) {
|
||||
fail("Recording start threw exception", e);
|
||||
} catch (IOException|IllegalStateException| JfrStreamingException e) {
|
||||
fail("assertRecordingStopState caught exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecordingClose() {
|
||||
public void assertRecordingCloseState() {
|
||||
assert flightRecorderConnection != null;
|
||||
Recording recording = flightRecorderConnection.newRecording(null, null);
|
||||
try {
|
||||
|
@ -120,8 +120,8 @@ public class RecordingTest {
|
|||
assertEquals(recording.getId(), id);
|
||||
recording.close();
|
||||
assertEquals(recording.getState(), Recording.State.CLOSED);
|
||||
} catch (IOException|IllegalStateException| InternalError e) {
|
||||
fail("Recording start threw exception", e);
|
||||
} catch (IOException|IllegalStateException| JfrStreamingException e) {
|
||||
fail("assertRecordingCloseState caught exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ public class RecordingTest {
|
|||
};
|
||||
|
||||
@Test(dataProvider = "validStateChanges")
|
||||
public void testValidStateChanges(Object[] args) {
|
||||
public void assertValidStateChangeNoException(Object[] args) {
|
||||
assert flightRecorderConnection != null;
|
||||
Recording recording = flightRecorderConnection.newRecording(null, null);
|
||||
try {
|
||||
|
@ -215,7 +215,7 @@ public class RecordingTest {
|
|||
};
|
||||
|
||||
@Test(dataProvider = "invalidStateChanges", expectedExceptions = {IllegalStateException.class})
|
||||
public void testInvalidStateChanges(Object[] args) {
|
||||
public void assertInvalidStateChangeThrowsIllegalStateException(Object[] args) {
|
||||
assert flightRecorderConnection != null;
|
||||
Recording recording = flightRecorderConnection.newRecording(null, null);
|
||||
try {
|
||||
|
@ -241,8 +241,9 @@ public class RecordingTest {
|
|||
{"name=test", "maxAge=30 s", "maxSize=1048576","dumpOnExit=true","destination=temp.jfr","disk=true","duration=30 s"},
|
||||
};
|
||||
}
|
||||
|
||||
@Test(dataProvider = "options")
|
||||
public void testRecordingOptions(String[] options) {
|
||||
public void assertRecordingOptionsAreSetInFlightRecorderMXBean(String[] options) {
|
||||
try {
|
||||
MBeanServerConnection mBeanServer = ManagementFactory.getPlatformMBeanServer();
|
||||
ObjectName flightRecorder = new ObjectName("jdk.management.jfr:type=FlightRecorder");
|
||||
|
@ -289,7 +290,7 @@ public class RecordingTest {
|
|||
} catch (IOException ioe) {
|
||||
// possible that this can be thrown, but should not happen in this context
|
||||
fail("IOException not expected: ", ioe);
|
||||
} catch (InternalError | ReflectionException| MBeanException badBean) {
|
||||
} catch (JfrStreamingException | ReflectionException| MBeanException badBean) {
|
||||
fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean);
|
||||
} catch (MalformedObjectNameException badTest) {
|
||||
fail("Error internal to the test: ", badTest);
|
||||
|
@ -314,9 +315,10 @@ public class RecordingTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testRecordingDump() {
|
||||
public void assertFileExistsAfterRecordingDump() {
|
||||
Path dumpFile = null;
|
||||
try {
|
||||
final Path dumpFile = Paths.get(System.getProperty("user.dir"),"testRecordingDump_dumped.jfr");
|
||||
dumpFile = Paths.get(System.getProperty("user.dir"),"testRecordingDump_dumped.jfr");
|
||||
Files.deleteIfExists(dumpFile);
|
||||
|
||||
RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build();
|
||||
|
@ -335,18 +337,70 @@ public class RecordingTest {
|
|||
} catch (IOException ioe) {
|
||||
// possible that this can be thrown, but should not happen in this context
|
||||
fail("IOException not expected: ", ioe);
|
||||
} catch (InternalError badBean) {
|
||||
} catch (JfrStreamingException badBean) {
|
||||
fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean);
|
||||
} finally {
|
||||
if (dumpFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(dumpFile);
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecordingStream() {
|
||||
public void assertFileExistsAfterRecordingStream() {
|
||||
Path streamedFile = null;
|
||||
try {
|
||||
final Path streamedFile = Paths.get(System.getProperty("user.dir"),"testRecordingStream_getStream.jfr");
|
||||
final Path dumpedFile = Paths.get(System.getProperty("user.dir"),"testRecordingStream_dumped.jfr");
|
||||
streamedFile = Paths.get(System.getProperty("user.dir"),"testRecordingStream_getStream.jfr");
|
||||
Files.deleteIfExists(streamedFile);
|
||||
|
||||
RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build();
|
||||
Recording recording = flightRecorderConnection.newRecording(recordingOptions, null);
|
||||
long id = recording.start();
|
||||
Instant now = Instant.now();
|
||||
Instant then = now.plusSeconds(1);
|
||||
while (Instant.now().compareTo(then) < 0) {
|
||||
fib(Short.MAX_VALUE); // do something
|
||||
}
|
||||
recording.stop();
|
||||
|
||||
try (InputStream inputStream = recording.getStream(now, then); // get the whole thing.
|
||||
OutputStream outputStream = new FileOutputStream(streamedFile.toFile())) {
|
||||
int c = -1;
|
||||
while ((c = inputStream.read()) != -1) outputStream.write(c);
|
||||
} catch (IOException e) {
|
||||
fail(e.getMessage(), e);
|
||||
}
|
||||
|
||||
assertTrue(Files.exists(streamedFile));
|
||||
|
||||
} catch (IllegalArgumentException badData) {
|
||||
fail("Issue in test data: " + badData.getMessage());
|
||||
} catch (IOException ioe) {
|
||||
// possible that this can be thrown, but should not happen in this context
|
||||
fail("IOException not expected: ", ioe);
|
||||
} catch (JfrStreamingException badBean) {
|
||||
fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean);
|
||||
} finally {
|
||||
if (streamedFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(streamedFile);
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertStreamedFileEqualsDumpedFile() {
|
||||
Path streamedFile = null;
|
||||
Path dumpedFile = null;
|
||||
try {
|
||||
streamedFile = Paths.get(System.getProperty("user.dir"),"testRecordingStream_getStream.jfr");
|
||||
dumpedFile = Paths.get(System.getProperty("user.dir"),"testRecordingStream_dumped.jfr");
|
||||
Files.deleteIfExists(streamedFile);
|
||||
Files.deleteIfExists(dumpedFile);
|
||||
|
||||
RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build();
|
||||
Recording recording = flightRecorderConnection.newRecording(recordingOptions, null);
|
||||
|
@ -365,9 +419,7 @@ public class RecordingTest {
|
|||
} catch (IOException e) {
|
||||
fail(e.getMessage(), e);
|
||||
}
|
||||
// save these in case this test fails so we can do the diff.
|
||||
jfrFilesToSave.add(streamedFile);
|
||||
jfrFilesToSave.add(dumpedFile);
|
||||
|
||||
try (InputStream streamed = new FileInputStream(streamedFile.toFile());
|
||||
InputStream dumped = new FileInputStream(dumpedFile.toFile())) {
|
||||
int a = -1;
|
||||
|
@ -381,21 +433,22 @@ public class RecordingTest {
|
|||
fail(e.getMessage(), e);
|
||||
}
|
||||
// if we get here, then the files compare the same and there is no need to save them
|
||||
jfrFilesToSave.remove(dumpedFile);
|
||||
jfrFilesToSave.remove(streamedFile);
|
||||
Files.deleteIfExists(dumpedFile);
|
||||
Files.deleteIfExists(streamedFile);
|
||||
} catch (IllegalArgumentException badData) {
|
||||
fail("Issue in test data: " + badData.getMessage());
|
||||
} catch (IOException ioe) {
|
||||
// possible that this can be thrown, but should not happen in this context
|
||||
fail("IOException not expected: ", ioe);
|
||||
} catch (InternalError badBean) {
|
||||
} catch (JfrStreamingException badBean) {
|
||||
fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecordingClone() {
|
||||
|
||||
public void assertRecordingCloneState() {
|
||||
// Recording#clone returns a clone of the recording with the same state, but clone has its own id.
|
||||
// Recording#clone with 'true' causes clone to close before returning.
|
||||
try {
|
||||
|
||||
RecordingOptions recordingOptions = new RecordingOptions.Builder().disk("true").build();
|
||||
|
@ -410,7 +463,7 @@ public class RecordingTest {
|
|||
} catch (IOException ioe) {
|
||||
// possible that this can be thrown, but should not happen in this context
|
||||
fail("IOException not expected: ", ioe);
|
||||
} catch (InternalError badBean) {
|
||||
} catch (JfrStreamingException badBean) {
|
||||
fail("Error thrown by MBean server or FlightRecorderMXBean: ", badBean);
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче