Skip to content

Commit

Permalink
feat(agent): implement HTTP-based recordings queries to -agents
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewazores committed Mar 21, 2023
1 parent e00cab4 commit ab599b2
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 81 deletions.
268 changes: 189 additions & 79 deletions src/main/java/io/cryostat/net/AgentClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,21 @@
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;
import org.openjdk.jmc.rjmx.services.jfr.IEventTypeInfo;
import org.openjdk.jmc.rjmx.services.jfr.IRecordingDescriptor;

import io.cryostat.configuration.CredentialsManager;
import io.cryostat.core.log.Logger;
Expand Down Expand Up @@ -123,92 +127,28 @@ Future<MBeanMetrics> mbeanMetrics() {
.map(s -> gson.fromJson(s, MBeanMetrics.class));
}

Future<List<IRecordingDescriptor>> activeRecordings() {
Future<HttpResponse<JsonArray>> f =
invoke(HttpMethod.GET, "/recordings", BodyCodec.jsonArray());
return f.map(HttpResponse::body)
.map(
arr ->
arr.stream()
.map(
o ->
(IRecordingDescriptor)
new AgentRecordingDescriptor(
(JsonObject) o))
.toList());
}

Future<Collection<? extends IEventTypeInfo>> eventTypes() {
Future<HttpResponse<JsonArray>> f =
invoke(HttpMethod.GET, "/event-types", BodyCodec.jsonArray());
return f.map(HttpResponse::body)
.map(arr -> arr.stream().map(o -> new AgentEventTypeInfo((JsonObject) o)).toList());
}

static class AgentEventTypeInfo implements IEventTypeInfo {

final JsonObject json;

AgentEventTypeInfo(JsonObject json) {
this.json = json;
}

@Override
public String getDescription() {
return json.getString("description");
}

@Override
public IEventTypeID getEventTypeID() {
return new EventTypeIDV2(json.getString("name"));
}

@Override
public String[] getHierarchicalCategory() {
return ((List<String>)
json.getJsonArray("categories").getList().stream()
.map(Object::toString)
.toList())
.toArray(new String[0]);
}

@Override
public String getName() {
return json.getString("name");
}

static <T, V> V capture(T t) {
// TODO clean up this generics hack
return (V) t;
}

@Override
public Map<String, ? extends IOptionDescriptor<?>> getOptionDescriptors() {
Map<String, ? extends IOptionDescriptor<?>> result = new HashMap<>();
JsonArray settings = json.getJsonArray("settings");
settings.forEach(
setting -> {
String name = ((JsonObject) setting).getString("name");
String defaultValue = ((JsonObject) setting).getString("defaultValue");
result.put(
name,
capture(
new IOptionDescriptor<String>() {
@Override
public String getName() {
return name;
}

@Override
public String getDescription() {
return null;
}

@Override
public IConstraint<String> getConstraint() {
return null;
}

@Override
public String getDefault() {
return defaultValue;
}
}));
});
return result;
}

@Override
public IOptionDescriptor<?> getOptionInfo(String s) {
return getOptionDescriptors().get(s);
}
}

Future<IConstrainedMap<EventOptionID>> eventSettings() {
Future<HttpResponse<JsonArray>> f =
invoke(HttpMethod.GET, "/event-settings", BodyCodec.jsonArray());
Expand Down Expand Up @@ -338,4 +278,174 @@ AgentClient create(URI agentUri) {
vertx, gson, httpTimeout, webClient, credentialsManager, agentUri, logger);
}
}

private static class AgentRecordingDescriptor implements IRecordingDescriptor {

final JsonObject json;

AgentRecordingDescriptor(JsonObject json) {
this.json = json;
}

@Override
public IQuantity getDataEndTime() {
// TODO
return UnitLookup.EPOCH_MS.quantity(-1L);
}

@Override
public IQuantity getDataStartTime() {
// TODO
return UnitLookup.EPOCH_MS.quantity(-1L);
}

@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() {
// TODO Auto-generated method stub
return null;
}

@Override
public Map<String, ?> 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;

AgentEventTypeInfo(JsonObject json) {
this.json = json;
}

@Override
public String getDescription() {
return json.getString("description");
}

@Override
public IEventTypeID getEventTypeID() {
return new EventTypeIDV2(json.getString("name"));
}

@Override
public String[] getHierarchicalCategory() {
return ((List<String>)
json.getJsonArray("categories").getList().stream()
.map(Object::toString)
.toList())
.toArray(new String[0]);
}

@Override
public String getName() {
return json.getString("name");
}

static <T, V> V capture(T t) {
// TODO clean up this generics hack
return (V) t;
}

@Override
public Map<String, ? extends IOptionDescriptor<?>> getOptionDescriptors() {
Map<String, ? extends IOptionDescriptor<?>> result = new HashMap<>();
JsonArray settings = json.getJsonArray("settings");
settings.forEach(
setting -> {
String name = ((JsonObject) setting).getString("name");
String defaultValue = ((JsonObject) setting).getString("defaultValue");
result.put(
name,
capture(
new IOptionDescriptor<String>() {
@Override
public String getName() {
return name;
}

@Override
public String getDescription() {
return null;
}

@Override
public IConstraint<String> getConstraint() {
return null;
}

@Override
public String getDefault() {
return defaultValue;
}
}));
});
return result;
}

@Override
public IOptionDescriptor<?> getOptionInfo(String s) {
return getOptionDescriptors().get(s);
}
}
}
8 changes: 6 additions & 2 deletions src/main/java/io/cryostat/net/AgentJFRService.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ public Map<String, IOptionDescriptor<?>> getAvailableRecordingOptions()

@Override
public List<IRecordingDescriptor> getAvailableRecordings() throws FlightRecorderException {
// TODO Auto-generated method stub
return List.of();
try {
return client.activeRecordings().toCompletionStage().toCompletableFuture().get();
} catch (ExecutionException | InterruptedException e) {
logger.warn(e);
return List.of();
}
}

@Override
Expand Down

0 comments on commit ab599b2

Please sign in to comment.