diff --git a/pom.xml b/pom.xml
index ff5911ff09..408e8085ec 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,7 +83,7 @@
2.47
- 2.21.1
+ 2.22.0
15.4
3.12.0
diff --git a/smoketest.sh b/smoketest.sh
index b06e5266d8..e89bb1abec 100755
--- a/smoketest.sh
+++ b/smoketest.sh
@@ -235,6 +235,7 @@ runDemoApps() {
--env CRYOSTAT_AGENT_TRUST_ALL="true" \
--env CRYOSTAT_AGENT_AUTHORIZATION="Basic $(echo user:pass | base64)" \
--env CRYOSTAT_AGENT_REGISTRATION_PREFER_JMX="true" \
+ --env CRYOSTAT_AGENT_API_WRITES_ENABLED="true" \
--rm -d quay.io/andrewazores/quarkus-test:latest
# copy a jboss-client.jar into /clientlib first
diff --git a/src/main/java/io/cryostat/jmc/serialization/HyperlinkedSerializableRecordingDescriptor.java b/src/main/java/io/cryostat/jmc/serialization/HyperlinkedSerializableRecordingDescriptor.java
index 8d276e1cb9..000952c577 100644
--- a/src/main/java/io/cryostat/jmc/serialization/HyperlinkedSerializableRecordingDescriptor.java
+++ b/src/main/java/io/cryostat/jmc/serialization/HyperlinkedSerializableRecordingDescriptor.java
@@ -19,6 +19,7 @@
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor;
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor.RecordingState;
+import io.cryostat.core.serialization.SerializableRecordingDescriptor;
import io.cryostat.recordings.RecordingMetadataManager.Metadata;
import org.apache.commons.lang3.builder.EqualsBuilder;
diff --git a/src/main/java/io/cryostat/jmc/serialization/SerializableRecordingDescriptor.java b/src/main/java/io/cryostat/jmc/serialization/SerializableRecordingDescriptor.java
deleted file mode 100644
index 83ab728c51..0000000000
--- a/src/main/java/io/cryostat/jmc/serialization/SerializableRecordingDescriptor.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright The Cryostat Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.cryostat.jmc.serialization;
-
-import org.openjdk.jmc.common.unit.QuantityConversionException;
-import org.openjdk.jmc.common.unit.UnitLookup;
-import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor;
-import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor.RecordingState;
-
-import org.apache.commons.lang3.builder.EqualsBuilder;
-import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-
-public class SerializableRecordingDescriptor {
-
- protected long id;
- protected String name;
- protected RecordingState state;
- protected long startTime;
- protected long duration;
- protected boolean continuous;
- protected boolean toDisk;
- protected long maxSize;
- protected long maxAge;
-
- public SerializableRecordingDescriptor(IRecordingDescriptor orig)
- throws QuantityConversionException {
- this.id = orig.getId();
- this.name = orig.getName();
- this.state = orig.getState();
- this.startTime = orig.getStartTime().longValueIn(UnitLookup.EPOCH_MS);
- this.duration = orig.getDuration().longValueIn(UnitLookup.MILLISECOND);
- this.continuous = orig.isContinuous();
- this.toDisk = orig.getToDisk();
- this.maxSize = orig.getMaxSize().longValueIn(UnitLookup.BYTE);
- this.maxAge = orig.getMaxAge().longValueIn(UnitLookup.MILLISECOND);
- }
-
- public SerializableRecordingDescriptor(SerializableRecordingDescriptor o) {
- this.id = o.getId();
- this.name = o.getName();
- this.state = o.getState();
- this.startTime = o.getStartTime();
- this.duration = o.getDuration();
- this.continuous = o.isContinuous();
- this.toDisk = o.getToDisk();
- this.maxSize = o.getMaxSize();
- this.maxAge = o.getMaxAge();
- }
-
- public long getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
-
- public RecordingState getState() {
- return state;
- }
-
- public long getStartTime() {
- return startTime;
- }
-
- public long getDuration() {
- return duration;
- }
-
- public boolean isContinuous() {
- return continuous;
- }
-
- public boolean getToDisk() {
- return toDisk;
- }
-
- public long getMaxSize() {
- return maxSize;
- }
-
- public long getMaxAge() {
- return maxAge;
- }
-
- @Override
- public String toString() {
- return ToStringBuilder.reflectionToString(this);
- }
-
- @Override
- public int hashCode() {
- return HashCodeBuilder.reflectionHashCode(this);
- }
-
- @Override
- public boolean equals(Object o) {
- return EqualsBuilder.reflectionEquals(this, o);
- }
-}
diff --git a/src/main/java/io/cryostat/net/AgentClient.java b/src/main/java/io/cryostat/net/AgentClient.java
index 84b81c6094..beed6a8b99 100644
--- a/src/main/java/io/cryostat/net/AgentClient.java
+++ b/src/main/java/io/cryostat/net/AgentClient.java
@@ -25,16 +25,13 @@
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
-import javax.management.ObjectName;
import javax.script.ScriptException;
import org.openjdk.jmc.common.unit.IConstrainedMap;
import org.openjdk.jmc.common.unit.IConstraint;
import org.openjdk.jmc.common.unit.IOptionDescriptor;
-import org.openjdk.jmc.common.unit.IQuantity;
import org.openjdk.jmc.common.unit.QuantityConversionException;
import org.openjdk.jmc.common.unit.SimpleConstrainedMap;
-import org.openjdk.jmc.common.unit.UnitLookup;
import org.openjdk.jmc.flightrecorder.configuration.events.EventOptionID;
import org.openjdk.jmc.flightrecorder.configuration.events.IEventTypeID;
import org.openjdk.jmc.flightrecorder.configuration.internal.EventTypeIDV2;
@@ -45,11 +42,15 @@
import io.cryostat.core.log.Logger;
import io.cryostat.core.net.Credentials;
import io.cryostat.core.net.MBeanMetrics;
+import io.cryostat.core.serialization.SerializableRecordingDescriptor;
+import io.cryostat.net.AgentJFRService.StartRecordingRequest;
import io.cryostat.util.HttpStatusCodeIdentifier;
import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
import io.vertx.core.Future;
import io.vertx.core.Vertx;
+import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
@@ -107,19 +108,40 @@ Future mbeanMetrics() {
.map(s -> gson.fromJson(s, MBeanMetrics.class));
}
+ Future startRecording(StartRecordingRequest req) {
+ Future> f =
+ invoke(
+ HttpMethod.POST,
+ "/recordings",
+ Buffer.buffer(gson.toJson(req)),
+ BodyCodec.string());
+ return f.map(
+ resp -> {
+ int statusCode = resp.statusCode();
+ if (HttpStatusCodeIdentifier.isSuccessCode(statusCode)) {
+ String body = resp.body();
+ return gson.fromJson(body, SerializableRecordingDescriptor.class)
+ .toJmcForm();
+ } else if (statusCode == 403) {
+ throw new UnsupportedOperationException();
+ } else {
+ throw new RuntimeException("Unknown failure");
+ }
+ });
+ }
+
Future> activeRecordings() {
- Future> f =
- invoke(HttpMethod.GET, "/recordings", BodyCodec.jsonArray());
+ Future> f = invoke(HttpMethod.GET, "/recordings", BodyCodec.string());
return f.map(HttpResponse::body)
.map(
- arr ->
- arr.stream()
- .map(
- o ->
- (IRecordingDescriptor)
- new AgentRecordingDescriptor(
- (JsonObject) o))
- .toList());
+ s ->
+ (List)
+ gson.fromJson(
+ s,
+ new TypeToken<
+ List<
+ SerializableRecordingDescriptor>>() {}.getType()))
+ .map(arr -> arr.stream().map(SerializableRecordingDescriptor::toJmcForm).toList());
}
Future> eventTypes() {
@@ -190,6 +212,11 @@ Future> eventTemplates() {
}
private Future> invoke(HttpMethod mtd, String path, BodyCodec codec) {
+ return invoke(mtd, path, null, codec);
+ }
+
+ private Future> invoke(
+ HttpMethod mtd, String path, Buffer payload, BodyCodec codec) {
return Future.fromCompletionStage(
CompletableFuture.supplyAsync(
() -> {
@@ -228,10 +255,17 @@ private Future> invoke(HttpMethod mtd, String path, BodyCode
}
try {
- return req.send()
- .toCompletionStage()
- .toCompletableFuture()
- .get();
+ if (payload != null) {
+ return req.sendBuffer(payload)
+ .toCompletionStage()
+ .toCompletableFuture()
+ .get();
+ } else {
+ return req.send()
+ .toCompletionStage()
+ .toCompletableFuture()
+ .get();
+ }
} catch (InterruptedException | ExecutionException e) {
logger.error(e);
throw new RuntimeException(e);
@@ -274,97 +308,6 @@ AgentClient create(URI agentUri) {
}
}
- private static class AgentRecordingDescriptor implements IRecordingDescriptor {
-
- final JsonObject json;
-
- AgentRecordingDescriptor(JsonObject json) {
- this.json = json;
- }
-
- @Override
- public IQuantity getDataStartTime() {
- return getStartTime();
- }
-
- @Override
- public IQuantity getDataEndTime() {
- if (isContinuous()) {
- return UnitLookup.EPOCH_MS.quantity(0);
- }
- return getDataStartTime().add(getDuration());
- }
-
- @Override
- public IQuantity getDuration() {
- return UnitLookup.MILLISECOND.quantity(json.getLong("duration"));
- }
-
- @Override
- public Long getId() {
- return json.getLong("id");
- }
-
- @Override
- public IQuantity getMaxAge() {
- return UnitLookup.MILLISECOND.quantity(json.getLong("maxAge"));
- }
-
- @Override
- public IQuantity getMaxSize() {
- return UnitLookup.BYTE.quantity(json.getLong("maxSize"));
- }
-
- @Override
- public String getName() {
- return json.getString("name");
- }
-
- @Override
- public ObjectName getObjectName() {
- return null;
- }
-
- @Override
- public Map getOptions() {
- return json.getJsonObject("options").getMap();
- }
-
- @Override
- public IQuantity getStartTime() {
- return UnitLookup.EPOCH_MS.quantity(json.getLong("startTime"));
- }
-
- @Override
- public RecordingState getState() {
- // avoid using Enum.valueOf() since that throws an exception if the name isn't part of
- // the type, and it's nicer to not throw and catch exceptions
- String state = json.getString("state");
- switch (state) {
- case "CREATED":
- return RecordingState.CREATED;
- case "RUNNING":
- return RecordingState.RUNNING;
- case "STOPPING":
- return RecordingState.STOPPING;
- case "STOPPED":
- return RecordingState.STOPPED;
- default:
- return RecordingState.RUNNING;
- }
- }
-
- @Override
- public boolean getToDisk() {
- return json.getBoolean("toDisk");
- }
-
- @Override
- public boolean isContinuous() {
- return json.getBoolean("isContinuous");
- }
- }
-
private static class AgentEventTypeInfo implements IEventTypeInfo {
final JsonObject json;
diff --git a/src/main/java/io/cryostat/net/AgentConnection.java b/src/main/java/io/cryostat/net/AgentConnection.java
index 65ce5dd072..cce516b2d6 100644
--- a/src/main/java/io/cryostat/net/AgentConnection.java
+++ b/src/main/java/io/cryostat/net/AgentConnection.java
@@ -28,14 +28,16 @@
import org.openjdk.jmc.rjmx.ConnectionException;
import org.openjdk.jmc.rjmx.IConnectionHandle;
import org.openjdk.jmc.rjmx.ServiceNotAvailableException;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
import io.cryostat.core.net.IDException;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.core.net.MBeanMetrics;
import io.cryostat.core.sys.Clock;
-import io.cryostat.core.templates.RemoteTemplateService;
+import io.cryostat.core.sys.Environment;
+import io.cryostat.core.sys.FileSystem;
+import io.cryostat.core.templates.MergedTemplateService;
import io.cryostat.core.templates.TemplateService;
import io.cryostat.recordings.JvmIdHelper;
@@ -45,11 +47,20 @@ public class AgentConnection implements JFRConnection {
private final AgentClient client;
private final JvmIdHelper idHelper;
+ private final FileSystem fs;
+ private final Environment env;
private final Logger logger;
- AgentConnection(AgentClient client, JvmIdHelper idHelper, Logger logger) {
+ AgentConnection(
+ AgentClient client,
+ JvmIdHelper idHelper,
+ FileSystem fs,
+ Environment env,
+ Logger logger) {
this.client = client;
this.idHelper = idHelper;
+ this.fs = fs;
+ this.env = env;
this.logger = logger;
}
@@ -112,14 +123,14 @@ public int getPort() {
}
@Override
- public IFlightRecorderService getService()
+ public CryostatFlightRecorderService getService()
throws ConnectionException, IOException, ServiceNotAvailableException {
- return new AgentJFRService(client, logger);
+ return new AgentJFRService(client, (MergedTemplateService) getTemplateService(), logger);
}
@Override
public TemplateService getTemplateService() {
- return new RemoteTemplateService(this);
+ return new MergedTemplateService(this, fs, env);
}
@Override
@@ -141,16 +152,25 @@ public MBeanMetrics getMBeanMetrics()
public static class Factory {
private final AgentClient.Factory clientFactory;
private final JvmIdHelper idHelper;
+ private final FileSystem fs;
+ private final Environment env;
private final Logger logger;
- Factory(AgentClient.Factory clientFactory, JvmIdHelper idHelper, Logger logger) {
+ Factory(
+ AgentClient.Factory clientFactory,
+ JvmIdHelper idHelper,
+ FileSystem fs,
+ Environment env,
+ Logger logger) {
this.clientFactory = clientFactory;
this.idHelper = idHelper;
+ this.fs = fs;
+ this.env = env;
this.logger = logger;
}
AgentConnection createConnection(URI agentUri) {
- return new AgentConnection(clientFactory.create(agentUri), idHelper, logger);
+ return new AgentConnection(clientFactory.create(agentUri), idHelper, fs, env, logger);
}
}
}
diff --git a/src/main/java/io/cryostat/net/AgentJFRService.java b/src/main/java/io/cryostat/net/AgentJFRService.java
index bc6d68a9e7..aaa791e5ab 100644
--- a/src/main/java/io/cryostat/net/AgentJFRService.java
+++ b/src/main/java/io/cryostat/net/AgentJFRService.java
@@ -15,7 +15,9 @@
*/
package io.cryostat.net;
+import java.io.IOException;
import java.io.InputStream;
+import java.text.ParseException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -26,34 +28,51 @@
import org.openjdk.jmc.common.unit.IDescribedMap;
import org.openjdk.jmc.common.unit.IOptionDescriptor;
import org.openjdk.jmc.common.unit.IQuantity;
+import org.openjdk.jmc.common.unit.ITypedQuantity;
+import org.openjdk.jmc.common.unit.QuantityConversionException;
+import org.openjdk.jmc.common.unit.UnitLookup;
import org.openjdk.jmc.flightrecorder.configuration.events.EventOptionID;
import org.openjdk.jmc.flightrecorder.configuration.events.IEventTypeID;
import org.openjdk.jmc.flightrecorder.configuration.internal.DefaultValueMap;
+import org.openjdk.jmc.flightrecorder.configuration.internal.KnownEventOptions;
+import org.openjdk.jmc.flightrecorder.configuration.internal.KnownRecordingOptions;
+import org.openjdk.jmc.flightrecorder.configuration.recording.RecordingOptionsBuilder;
+import org.openjdk.jmc.rjmx.ConnectionException;
+import org.openjdk.jmc.rjmx.ServiceNotAvailableException;
import org.openjdk.jmc.rjmx.services.jfr.FlightRecorderException;
import org.openjdk.jmc.rjmx.services.jfr.IEventTypeInfo;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor;
+import io.cryostat.core.EventOptionsBuilder.EventOptionException;
+import io.cryostat.core.EventOptionsBuilder.EventTypeException;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
+import io.cryostat.core.templates.MergedTemplateService;
+import io.cryostat.core.templates.Template;
+import io.cryostat.core.templates.TemplateType;
-class AgentJFRService implements IFlightRecorderService {
+import org.jsoup.nodes.Document;
+
+class AgentJFRService implements CryostatFlightRecorderService {
private final AgentClient client;
+ private final MergedTemplateService templateService;
private final Logger logger;
- AgentJFRService(AgentClient client, Logger logger) {
+ AgentJFRService(AgentClient client, MergedTemplateService templateService, Logger logger) {
this.client = client;
+ this.templateService = templateService;
this.logger = logger;
}
@Override
public IDescribedMap getDefaultEventOptions() {
- return new DefaultValueMap<>(Map.of());
+ return KnownEventOptions.OPTION_DEFAULTS_V2;
}
@Override
public IDescribedMap getDefaultRecordingOptions() {
- return new DefaultValueMap<>(Map.of());
+ return KnownRecordingOptions.OPTION_DEFAULTS_V2;
}
@Override
@@ -85,8 +104,7 @@ public Collection extends IEventTypeInfo> getAvailableEventTypes()
@Override
public Map> getAvailableRecordingOptions()
throws FlightRecorderException {
- // TODO Auto-generated method stub
- return Map.of();
+ return KnownRecordingOptions.DESCRIPTORS_BY_KEY_V2;
}
@Override
@@ -203,5 +221,84 @@ public void updateRecordingOptions(IRecordingDescriptor arg0, IConstrainedMap recordingOptions,
+ String templateName,
+ TemplateType preferredTemplateType)
+ throws io.cryostat.core.FlightRecorderException, FlightRecorderException,
+ ConnectionException, IOException, ServiceNotAvailableException,
+ QuantityConversionException, EventOptionException, EventTypeException {
+ StartRecordingRequest req;
+ String recordingName = recordingOptions.get("name").toString();
+ long duration =
+ (Optional.ofNullable(
+ (ITypedQuantity)
+ recordingOptions.get(
+ RecordingOptionsBuilder.KEY_DURATION))
+ .orElse(UnitLookup.MILLISECOND.quantity(0)))
+ .longValueIn(UnitLookup.MILLISECOND);
+ long maxSize =
+ (Optional.ofNullable(
+ (ITypedQuantity)
+ recordingOptions.get(
+ RecordingOptionsBuilder.KEY_MAX_SIZE))
+ .orElse(UnitLookup.BYTE.quantity(0)))
+ .longValueIn(UnitLookup.BYTE);
+ long maxAge =
+ (Optional.ofNullable(
+ (ITypedQuantity)
+ recordingOptions.get(
+ RecordingOptionsBuilder.KEY_MAX_AGE))
+ .orElse(UnitLookup.MILLISECOND.quantity(0)))
+ .longValueIn(UnitLookup.MILLISECOND);
+ if (preferredTemplateType.equals(TemplateType.CUSTOM)) {
+ req =
+ new StartRecordingRequest(
+ recordingName,
+ null,
+ templateService
+ .getXml(templateName, preferredTemplateType)
+ .orElseThrow()
+ .outerHtml(),
+ duration,
+ maxSize,
+ maxAge);
+ } else {
+ req =
+ new StartRecordingRequest(
+ recordingName, templateName, null, duration, maxSize, maxAge);
+ }
+ try {
+ return client.startRecording(req).toCompletionStage().toCompletableFuture().get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new io.cryostat.core.FlightRecorderException(e);
+ }
+ }
+
+ @Override
+ public IRecordingDescriptor start(
+ IConstrainedMap recordingOptions, Template eventTemplate)
+ throws io.cryostat.core.FlightRecorderException, FlightRecorderException,
+ ConnectionException, IOException, FlightRecorderException,
+ ServiceNotAvailableException, QuantityConversionException, EventOptionException,
+ EventTypeException {
+ return CryostatFlightRecorderService.super.start(recordingOptions, eventTemplate);
+ }
+
+ @Override
+ public IRecordingDescriptor start(IConstrainedMap recordingOptions, Document template)
+ throws FlightRecorderException, ParseException, IOException {
+ throw new UnimplementedException();
+ }
+
public static class UnimplementedException extends IllegalStateException {}
+
+ static record StartRecordingRequest(
+ String name,
+ String localTemplateName,
+ String template,
+ long duration,
+ long maxSize,
+ long maxAge) {}
}
diff --git a/src/main/java/io/cryostat/net/NetworkModule.java b/src/main/java/io/cryostat/net/NetworkModule.java
index d7cffcc02d..bc28df84b2 100644
--- a/src/main/java/io/cryostat/net/NetworkModule.java
+++ b/src/main/java/io/cryostat/net/NetworkModule.java
@@ -97,8 +97,12 @@ static Duration provideMaxTargetTTL(Environment env) {
@Provides
@Singleton
static AgentConnection.Factory provideAgentConnectionFactory(
- AgentClient.Factory clientFactory, JvmIdHelper idHelper, Logger logger) {
- return new AgentConnection.Factory(clientFactory, idHelper, logger);
+ AgentClient.Factory clientFactory,
+ JvmIdHelper idHelper,
+ FileSystem fs,
+ Environment env,
+ Logger logger) {
+ return new AgentConnection.Factory(clientFactory, idHelper, fs, env, logger);
}
@Provides
diff --git a/src/main/java/io/cryostat/recordings/EventOptionsBuilder.java b/src/main/java/io/cryostat/recordings/EventOptionsBuilder.java
deleted file mode 100644
index 9266df3d48..0000000000
--- a/src/main/java/io/cryostat/recordings/EventOptionsBuilder.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright The Cryostat Authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package io.cryostat.recordings;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.function.Supplier;
-
-import org.openjdk.jmc.common.unit.IConstrainedMap;
-import org.openjdk.jmc.common.unit.IConstraint;
-import org.openjdk.jmc.common.unit.IMutableConstrainedMap;
-import org.openjdk.jmc.common.unit.IOptionDescriptor;
-import org.openjdk.jmc.flightrecorder.configuration.events.EventOptionID;
-import org.openjdk.jmc.flightrecorder.configuration.events.IEventTypeID;
-import org.openjdk.jmc.rjmx.IConnectionHandle;
-import org.openjdk.jmc.rjmx.services.jfr.IEventTypeInfo;
-import org.openjdk.jmc.rjmx.services.jfr.internal.FlightRecorderServiceV2;
-
-import io.cryostat.core.net.JFRConnection;
-import io.cryostat.core.tui.ClientWriter;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-public class EventOptionsBuilder {
-
- private final boolean isV2;
- private final IMutableConstrainedMap map;
- private Map>> knownTypes;
- private Map eventIds;
-
- // Testing only
- EventOptionsBuilder(ClientWriter cw, JFRConnection connection, Supplier v2)
- throws Exception {
- this.isV2 = v2.get();
- this.map = connection.getService().getDefaultEventOptions().emptyWithSameConstraints();
- knownTypes = new HashMap<>();
- eventIds = new HashMap<>();
-
- if (!isV2) {
- cw.println("Flight Recorder V1 is not yet supported");
- }
-
- for (IEventTypeInfo eventTypeInfo : connection.getService().getAvailableEventTypes()) {
- eventIds.put(
- eventTypeInfo.getEventTypeID().getFullKey(), eventTypeInfo.getEventTypeID());
- knownTypes.putIfAbsent(
- eventTypeInfo.getEventTypeID(),
- new HashMap<>(eventTypeInfo.getOptionDescriptors()));
- }
- }
-
- public EventOptionsBuilder addEvent(String typeId, String option, String value)
- throws Exception {
- if (!eventIds.containsKey(typeId)) {
- throw new EventTypeException(typeId);
- }
- Map> optionDescriptors = knownTypes.get(eventIds.get(typeId));
- if (!optionDescriptors.containsKey(option)) {
- throw new EventOptionException(typeId, option);
- }
- IConstraint> constraint = optionDescriptors.get(option).getConstraint();
- Object parsedValue = constraint.parseInteractive(value);
- constraint.validate(capture(parsedValue));
- this.map.put(new EventOptionID(eventIds.get(typeId), option), parsedValue);
-
- return this;
- }
-
- static V capture(T t) {
- // TODO clean up this generics hack
- return (V) t;
- }
-
- @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "Field is never mutated")
- public IConstrainedMap build() {
- if (!isV2) {
- return null;
- }
- return map;
- }
-
- public static class EventTypeException extends Exception {
- EventTypeException(String eventType) {
- super(String.format("Unknown event type \"%s\"", eventType));
- }
- }
-
- static class EventOptionException extends Exception {
- EventOptionException(String eventType, String option) {
- super(String.format("Unknown option \"%s\" for event \"%s\"", option, eventType));
- }
- }
-
- public static class Factory {
- private final ClientWriter cw;
-
- public Factory(ClientWriter cw) {
- this.cw = cw;
- }
-
- public EventOptionsBuilder create(JFRConnection connection) throws Exception {
- IConnectionHandle handle = connection.getHandle();
- return new EventOptionsBuilder(
- cw, connection, () -> FlightRecorderServiceV2.isAvailable(handle));
- }
- }
-}
diff --git a/src/main/java/io/cryostat/recordings/RecordingTargetHelper.java b/src/main/java/io/cryostat/recordings/RecordingTargetHelper.java
index e0e9e7bdbe..897cbbae8f 100644
--- a/src/main/java/io/cryostat/recordings/RecordingTargetHelper.java
+++ b/src/main/java/io/cryostat/recordings/RecordingTargetHelper.java
@@ -36,6 +36,7 @@
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor;
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor.RecordingState;
+import io.cryostat.core.EventOptionsBuilder;
import io.cryostat.core.log.Logger;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.core.templates.Template;
@@ -167,12 +168,7 @@ public IRecordingDescriptor startRecording(
IRecordingDescriptor desc =
connection
.getService()
- .start(
- recordingOptions,
- enableEvents(
- connection,
- templateName,
- preferredTemplateType));
+ .start(recordingOptions, templateName, preferredTemplateType);
String targetId = connectionDescriptor.getTargetId();
Map labels = metadata.getLabels();
@@ -595,7 +591,11 @@ private void scheduleRecordingTasks(
connection, name));
return linked;
}
- return null;
+ throw new IllegalStateException(
+ String.format(
+ "Could not find expected recording"
+ + " named \"%s\" in target %s",
+ recordingName, targetId));
});
promise.complete(linkedDesc);
} catch (Exception e) {
diff --git a/src/main/java/io/cryostat/recordings/RecordingsModule.java b/src/main/java/io/cryostat/recordings/RecordingsModule.java
index 3851c06678..45aed6d95a 100644
--- a/src/main/java/io/cryostat/recordings/RecordingsModule.java
+++ b/src/main/java/io/cryostat/recordings/RecordingsModule.java
@@ -33,6 +33,7 @@
import io.cryostat.configuration.ConfigurationModule;
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.configuration.Variables;
+import io.cryostat.core.EventOptionsBuilder;
import io.cryostat.core.RecordingOptionsCustomizer;
import io.cryostat.core.log.Logger;
import io.cryostat.core.sys.Clock;
diff --git a/src/test/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandlerTest.java b/src/test/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandlerTest.java
index d6d4565142..95cb5e5b8d 100644
--- a/src/test/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandlerTest.java
+++ b/src/test/java/io/cryostat/net/web/http/api/v1/TargetEventsGetHandlerTest.java
@@ -23,11 +23,11 @@
import org.openjdk.jmc.flightrecorder.configuration.events.IEventTypeID;
import org.openjdk.jmc.rjmx.services.jfr.IEventTypeInfo;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
import io.cryostat.MainModule;
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.jmc.serialization.SerializableEventTypeInfo;
import io.cryostat.net.AuthManager;
@@ -107,7 +107,7 @@ void shouldRespondWithErrorIfExceptionThrown() throws Exception {
@Test
void shouldRespondWithEventsList() throws Exception {
JFRConnection connection = Mockito.mock(JFRConnection.class);
- IFlightRecorderService service = Mockito.mock(IFlightRecorderService.class);
+ CryostatFlightRecorderService service = Mockito.mock(CryostatFlightRecorderService.class);
IEventTypeInfo event1 = Mockito.mock(IEventTypeInfo.class);
IEventTypeID eventTypeId1 = Mockito.mock(IEventTypeID.class);
diff --git a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandlerTest.java b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandlerTest.java
index 9d392d2070..257a633876 100644
--- a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandlerTest.java
+++ b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingGetHandlerTest.java
@@ -28,10 +28,9 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
-
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.net.AuthManager;
import io.cryostat.net.HttpServer;
@@ -78,7 +77,7 @@ class TargetRecordingGetHandlerTest {
@Mock Logger logger;
@Mock JFRConnection connection;
- @Mock IFlightRecorderService service;
+ @Mock CryostatFlightRecorderService service;
@BeforeEach
void setup() {
diff --git a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandlerTest.java b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandlerTest.java
index a17c2ae386..9931a51408 100644
--- a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandlerTest.java
+++ b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsGetHandlerTest.java
@@ -20,11 +20,11 @@
import org.openjdk.jmc.common.unit.IConstrainedMap;
import org.openjdk.jmc.flightrecorder.configuration.recording.RecordingOptionsBuilder;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
import io.cryostat.MainModule;
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
@@ -151,7 +151,7 @@ public Map answer(InvocationOnMock args) throws Throwable {
resp.putHeader(
Mockito.any(CharSequence.class), Mockito.any(CharSequence.class)))
.thenReturn(resp);
- IFlightRecorderService service = Mockito.mock(IFlightRecorderService.class);
+ CryostatFlightRecorderService service = Mockito.mock(CryostatFlightRecorderService.class);
Mockito.when(jfrConnection.getService()).thenReturn(service);
handler.handleAuthenticated(ctx);
diff --git a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandlerTest.java b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandlerTest.java
index fa18b44b18..cabe9981b4 100644
--- a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandlerTest.java
+++ b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingOptionsPatchHandlerTest.java
@@ -21,12 +21,12 @@
import org.openjdk.jmc.common.unit.IConstrainedMap;
import org.openjdk.jmc.flightrecorder.configuration.recording.RecordingOptionsBuilder;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.core.RecordingOptionsCustomizer;
import io.cryostat.core.RecordingOptionsCustomizer.OptionKey;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.net.AuthManager;
import io.cryostat.net.ConnectionDescriptor;
@@ -136,7 +136,7 @@ public Map answer(InvocationOnMock args) throws Throwable {
Mockito.when(req.formAttributes()).thenReturn(requestAttrs);
HttpServerResponse resp = Mockito.mock(HttpServerResponse.class);
Mockito.when(ctx.response()).thenReturn(resp);
- IFlightRecorderService service = Mockito.mock(IFlightRecorderService.class);
+ CryostatFlightRecorderService service = Mockito.mock(CryostatFlightRecorderService.class);
Mockito.when(jfrConnection.getService()).thenReturn(service);
handler.handleAuthenticated(ctx);
@@ -182,7 +182,7 @@ public Map answer(InvocationOnMock args) throws Throwable {
Mockito.when(req.formAttributes()).thenReturn(requestAttrs);
HttpServerResponse resp = Mockito.mock(HttpServerResponse.class);
Mockito.when(ctx.response()).thenReturn(resp);
- IFlightRecorderService service = Mockito.mock(IFlightRecorderService.class);
+ CryostatFlightRecorderService service = Mockito.mock(CryostatFlightRecorderService.class);
Mockito.when(jfrConnection.getService()).thenReturn(service);
handler.handleAuthenticated(ctx);
diff --git a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandlerTest.java b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandlerTest.java
index 69bda34d8a..6c18b04a0a 100644
--- a/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandlerTest.java
+++ b/src/test/java/io/cryostat/net/web/http/api/v1/TargetRecordingUploadPostHandlerTest.java
@@ -22,11 +22,11 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import org.openjdk.jmc.rjmx.services.jfr.IFlightRecorderService;
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor;
import io.cryostat.configuration.CredentialsManager;
import io.cryostat.core.log.Logger;
+import io.cryostat.core.net.CryostatFlightRecorderService;
import io.cryostat.core.net.JFRConnection;
import io.cryostat.core.sys.Environment;
import io.cryostat.core.sys.FileSystem;
@@ -164,7 +164,7 @@ void shouldThrowExceptionIfRecordingNotFound() throws Exception {
((TargetConnectionManager.ConnectedTask