From 8fe451e7da8ed92a1f9de8a4a3adc2659ae1dffd Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Fri, 6 Dec 2024 14:46:42 -0500 Subject: [PATCH] feat(matchexpressions): expressions can reference target JFR event type IDs --- .../expressions/MatchExpressionEvaluator.java | 73 ++++++++++++++++++- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java b/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java index 3053baee8..58d8efd33 100644 --- a/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java +++ b/src/main/java/io/cryostat/expressions/MatchExpressionEvaluator.java @@ -23,15 +23,18 @@ import java.util.concurrent.CompletionException; import java.util.stream.Collectors; +import io.cryostat.events.SerializableEventTypeInfo; import io.cryostat.expressions.MatchExpression.ExpressionEvent; import io.cryostat.targets.Target; import io.cryostat.targets.Target.Annotations; import io.cryostat.targets.Target.TargetDiscovery; +import io.cryostat.targets.TargetConnectionManager; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.quarkus.cache.CacheManager; import io.quarkus.cache.CacheResult; import io.quarkus.cache.CompositeCacheKey; +import io.quarkus.logging.Log; import io.quarkus.vertx.ConsumeEvent; import jakarta.annotation.Nullable; import jakarta.enterprise.context.ApplicationScoped; @@ -42,7 +45,12 @@ import jdk.jfr.Label; import jdk.jfr.Name; import org.jboss.logging.Logger; +import org.projectnessie.cel.EnvOption; +import org.projectnessie.cel.Library; +import org.projectnessie.cel.ProgramOption; import org.projectnessie.cel.checker.Decls; +import org.projectnessie.cel.common.types.ListT; +import org.projectnessie.cel.interpreter.functions.Overload; import org.projectnessie.cel.tools.Script; import org.projectnessie.cel.tools.ScriptCreateException; import org.projectnessie.cel.tools.ScriptException; @@ -56,6 +64,7 @@ public class MatchExpressionEvaluator { @Inject ScriptHost scriptHost; @Inject Logger logger; @Inject CacheManager cacheManager; + @Inject TargetConnectionManager connectionManager; @ConsumeEvent(value = MatchExpression.EXPRESSION_ADDRESS, blocking = true) void onMessage(ExpressionEvent event) { @@ -93,7 +102,12 @@ void onMessage(TargetDiscovery event) { } } + // used only for validating script syntax without an actual Target to evaluate against Script createScript(String matchExpression) throws ScriptCreateException { + return createScript(null, matchExpression); + } + + Script createScript(Target target, String matchExpression) throws ScriptCreateException { ScriptCreation evt = new ScriptCreation(); try { evt.begin(); @@ -104,6 +118,7 @@ Script createScript(String matchExpression) throws ScriptCreateException { Decls.newVar( "target", Decls.newObjectType(SimplifiedTarget.class.getName()))) + .withLibraries(List.of(new EventTypesLibrary(connectionManager, target))) .build(); } finally { evt.end(); @@ -114,8 +129,8 @@ Script createScript(String matchExpression) throws ScriptCreateException { } @CacheResult(cacheName = CACHE_NAME) - boolean load(String matchExpression, Target target) throws ScriptException { - Script script = createScript(matchExpression); + boolean load(Target target, String matchExpression) throws ScriptException { + Script script = createScript(target, matchExpression); return script.execute(Boolean.class, Map.of("target", SimplifiedTarget.from(target))); } @@ -149,7 +164,7 @@ public boolean applies(MatchExpression matchExpression, Target target) throws Sc MatchExpressionApplies evt = new MatchExpressionApplies(matchExpression); try { evt.begin(); - return load(matchExpression.script, target); + return load(target, matchExpression.script); } catch (CompletionException e) { if (e.getCause() instanceof ScriptException) { throw (ScriptException) e.getCause(); @@ -248,4 +263,56 @@ static SimplifiedTarget from(Target target) { target.annotations); } } + + static class EventTypesLibrary implements Library { + + private final TargetConnectionManager connectionManager; + private final Target target; + + EventTypesLibrary(TargetConnectionManager connectionManager, Target target) { + this.connectionManager = connectionManager; + this.target = target; + } + + @Override + public List getCompileOptions() { + return List.of( + EnvOption.declarations( + Decls.newFunction( + "eventTypeIds", + Decls.newOverload( + "eventTypeIds_void", + Collections.emptyList(), + Decls.newListType(Decls.String))))); + } + + @Override + public List getProgramOptions() { + return List.of( + ProgramOption.functions( + Overload.function( + "eventTypeIds", + (args) -> ListT.newStringArrayList(getEventTypeIds())))); + } + + private String[] getEventTypeIds() { + if (target == null) { + return new String[0]; + } + try { + return connectionManager.executeConnectedTask( + target, + connection -> + connection.getService().getAvailableEventTypes().stream() + .map(SerializableEventTypeInfo::fromEventTypeInfo) + .map(SerializableEventTypeInfo::typeId) + .distinct() + .toList() + .toArray(new String[0])); + } catch (Exception e) { + Log.error(e); + return new String[0]; + } + } + } }