Skip to content

Commit

Permalink
feat(recordings): implement JFR snapshot (cryostatio#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewazores authored Nov 22, 2023
1 parent 6881045 commit 82f8802
Show file tree
Hide file tree
Showing 9 changed files with 540 additions and 455 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
<com.google.java-format.version>1.18.1</com.google.java-format.version>
<com.mycila.license.maven.plugin.version>4.3</com.mycila.license.maven.plugin.version>
<surefire-plugin.version>3.2.2</surefire-plugin.version>
<surefire.rerunFailingTestsCount>5</surefire.rerunFailingTestsCount>
<surefire.rerunFailingTestsCount>2</surefire.rerunFailingTestsCount>
<failsafe-plugin.version>${surefire-plugin.version}</failsafe-plugin.version>
<failsafe.rerunFailingTestsCount>${surefire.rerunFailingTestsCount}</failsafe.rerunFailingTestsCount>
<com.google.code.gson.version>2.10.1</com.google.code.gson.version><!-- TODO drop this -->
Expand Down
1 change: 0 additions & 1 deletion src/main/java/io/cryostat/V2Response.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@ public record Meta(String type, String status) {

public record Data(@Nullable Object result) {}
}
// {"meta":{"type":"application/json","status":"OK"},"data":{"result":{"name":"Test_Rule","description":"This is a rule for testing","matchExpression":"target.alias=='io.cryostat.Cryostat'","eventSpecifier":"template=Continuous,type=TARGET","archivalPeriodSeconds":30,"preservedArchives":1,"maxAgeSeconds":30,"maxSizeBytes":-1}}}
73 changes: 73 additions & 0 deletions src/main/java/io/cryostat/recordings/RecordingHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,72 @@ public ActiveRecording startRecording(
return recording;
}

public ActiveRecording createSnapshot(Target target, JFRConnection connection)
throws Exception {
IRecordingDescriptor desc = connection.getService().getSnapshotRecording();

String rename = String.format("%s-%d", desc.getName().toLowerCase(), desc.getId());

RecordingOptionsBuilder recordingOptionsBuilder =
recordingOptionsBuilderFactory.create(connection.getService());
recordingOptionsBuilder.name(rename);

connection.getService().updateRecordingOptions(desc, recordingOptionsBuilder.build());

Optional<IRecordingDescriptor> updatedDescriptor = getDescriptorByName(connection, rename);

if (updatedDescriptor.isEmpty()) {
throw new IllegalStateException(
"The most recent snapshot of the recording cannot be"
+ " found after renaming.");
}

desc = updatedDescriptor.get();

try (InputStream snapshot = remoteRecordingStreamFactory.open(connection, target, desc)) {
if (!snapshotIsReadable(target, snapshot)) {
connection.getService().close(desc);
throw new SnapshotCreationException(
"Snapshot was not readable - are there any source recordings?");
}
}

ActiveRecording recording =
ActiveRecording.from(
target,
desc,
new Metadata(
Map.of(
"jvmId",
target.jvmId,
"connectUrl",
target.connectUrl.toString())));
recording.persist();

target.activeRecordings.add(recording);
target.persist();

bus.publish(
MessagingServer.class.getName(),
new Notification(
"SnapshotCreated", new RecordingEvent(target.connectUrl, recording)));

return recording;
}

private boolean snapshotIsReadable(Target target, InputStream snapshot) throws IOException {
if (!connectionManager.markConnectionInUse(target)) {
throw new IOException(
"Target connection unexpectedly closed while streaming recording");
}

try {
return snapshot.read() != -1;
} catch (IOException e) {
return false;
}
}

private boolean shouldRestartRecording(
RecordingReplace replace, RecordingState state, String recordingName)
throws BadRequestException {
Expand All @@ -212,6 +278,7 @@ private boolean shouldRestartRecording(

public LinkedRecordingDescriptor toExternalForm(ActiveRecording recording) {
return new LinkedRecordingDescriptor(
recording.id,
recording.remoteId,
recording.state,
recording.duration,
Expand Down Expand Up @@ -769,4 +836,10 @@ public RecordingNotFoundException(Pair<String, String> key) {
this(key.getLeft(), key.getRight());
}
}

static class SnapshotCreationException extends Exception {
public SnapshotCreationException(String message) {
super(message);
}
}
}
84 changes: 67 additions & 17 deletions src/main/java/io/cryostat/recordings/Recordings.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@

import io.cryostat.ConfigProperties;
import io.cryostat.Producers;
import io.cryostat.V2Response;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.core.sys.Clock;
import io.cryostat.core.templates.TemplateType;
import io.cryostat.recordings.ActiveRecording.Listener.RecordingEvent;
import io.cryostat.recordings.RecordingHelper.RecordingReplace;
import io.cryostat.recordings.RecordingHelper.SnapshotCreationException;
import io.cryostat.targets.Target;
import io.cryostat.targets.TargetConnectionManager;
import io.cryostat.util.HttpStatusCodeIdentifier;
Expand Down Expand Up @@ -491,6 +493,69 @@ public Response patchV1(@RestPath URI connectUrl, @RestPath String recordingName
.build();
}

@POST
@Transactional
@Blocking
@Path("/api/v1/targets/{connectUrl}/snapshot")
@RolesAllowed("write")
public Response createSnapshotV1(@RestPath URI connectUrl) throws Exception {
Target target = Target.getTargetByConnectUrl(connectUrl);
try {
ActiveRecording recording =
connectionManager.executeConnectedTask(
target,
connection -> recordingHelper.createSnapshot(target, connection));
return Response.status(Response.Status.OK).entity(recording.name).build();
} catch (SnapshotCreationException sce) {
return Response.status(Response.Status.ACCEPTED).build();
}
}

@POST
@Transactional
@Blocking
@Path("/api/v2/targets/{connectUrl}/snapshot")
@RolesAllowed("write")
public Response createSnapshotV2(@RestPath URI connectUrl) throws Exception {
Target target = Target.getTargetByConnectUrl(connectUrl);
try {
ActiveRecording recording =
connectionManager.executeConnectedTask(
target,
connection -> recordingHelper.createSnapshot(target, connection));
return Response.status(Response.Status.CREATED)
.entity(
V2Response.json(
recordingHelper.toExternalForm(recording),
RestResponse.Status.CREATED.toString()))
.build();
} catch (SnapshotCreationException sce) {
return Response.status(Response.Status.ACCEPTED)
.entity(V2Response.json(null, RestResponse.Status.ACCEPTED.toString()))
.build();
}
}

@POST
@Transactional
@Blocking
@Path("/api/v3/targets/{id}/snapshot")
@RolesAllowed("write")
public Response createSnapshot(@RestPath long id) throws Exception {
Target target = Target.find("id", id).singleResult();
try {
ActiveRecording recording =
connectionManager.executeConnectedTask(
target,
connection -> recordingHelper.createSnapshot(target, connection));
return Response.status(Response.Status.OK)
.entity(recordingHelper.toExternalForm(recording))
.build();
} catch (SnapshotCreationException sce) {
return Response.status(Response.Status.ACCEPTED).build();
}
}

@POST
@Transactional
@Blocking
Expand Down Expand Up @@ -576,7 +641,7 @@ public Response createRecording(
}

return Response.status(Response.Status.CREATED)
.entity(recordingHelper.toExternalForm(recording).toString())
.entity(recordingHelper.toExternalForm(recording))
.build();
}

Expand Down Expand Up @@ -873,6 +938,7 @@ private static Long getNumericOption(

public record LinkedRecordingDescriptor(
long id,
long remoteId,
RecordingState state,
long duration,
long startTime,
Expand All @@ -891,22 +957,6 @@ public record LinkedRecordingDescriptor(
Objects.requireNonNull(reportUrl);
Objects.requireNonNull(metadata);
}

public static LinkedRecordingDescriptor from(ActiveRecording recording) {
return new LinkedRecordingDescriptor(
recording.remoteId,
recording.state,
recording.duration,
recording.startTime,
recording.continuous,
recording.toDisk,
recording.maxSize,
recording.maxAge,
recording.name,
"TODO",
"TODO",
recording.metadata);
}
}

public record ArchivedRecording(
Expand Down
Loading

0 comments on commit 82f8802

Please sign in to comment.