From ef358de0ac87c4b8889536131128cdb16940dcaa Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Oct 2023 16:36:08 -0400 Subject: [PATCH 01/10] allow single-quoted durations --- src/main/java/io/cryostat/agent/triggers/SmartTrigger.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java index d8240749..0094bfde 100644 --- a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java +++ b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java @@ -24,7 +24,7 @@ public class SmartTrigger { private static final String DURATION_PATTERN = - "(.+)(?:[&|]{2})(TargetDuration[<>=]+duration\\(\"(\\d+[sSmMhH]+)\"\\))"; + "(.+)(?:[&|]{2})(TargetDuration[<>=]+duration\\(['\"](\\d+[sSmMhH]+)['\"]\\))"; private static final Pattern durationPattern = Pattern.compile(DURATION_PATTERN); public enum TriggerState { @@ -56,7 +56,7 @@ public SmartTrigger(String expression, String templateName) { Matcher m = durationPattern.matcher(expression); if (m.matches()) { triggerCondition = m.group(1); - durationConstraint = m.group(2); + durationConstraint = m.group(2).replaceAll("'", "\""); /* Duration.parse requires timestamps in ISO8601 Duration format */ targetDuration = Duration.parse("PT" + m.group(3)); } else { From c601429e2f3245f52dbcb27b67cd95ed502897da Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Wed, 4 Oct 2023 16:53:18 -0400 Subject: [PATCH 02/10] allow complex cel conditions --- README.md | 15 ++++++++++++++- .../io/cryostat/agent/triggers/SmartTrigger.java | 9 +++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 34f02fe2..5e9961b4 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,20 @@ An example for watching for the Thread Count to exceed 20 for longer than 10 sec Continuous template: ``` -[ThreadCount>20&&TargetDuration>duration("10s")]~Continuous +[ThreadCount>20;TargetDuration>duration("10s")]~Continuous +``` + +The first part of the condition before the semicolon is a [Common Expression Language](https://github.com/google/cel-spec) +expression for testing various MBean metrics. The second part after the semicolon references a special variable, +`TargetDuration`, which tracks the length of time that the first part of the condition has tested `true` for. This is +converted to a `java.time.Duration` object and compared to `duration("10s")`, a special construct that is also +converted into a `java.time.Duration` object representing the time threshold before this trigger activates. The +`duration()` construct requires a `String` argument, which may be enclosed in single `'` or double `"` quotation marks. + +Smart Triggers may define more complex conditions that test multiple metrics: + +``` +[(ProcessCpuLoad>0.5||SystemCpuLoad>0.25)&&HeapUsagePercent>0.1;TargetDuration>duration('1m')]~Continuous ``` These may be passed as an argument to the Cryostat Agent, for example: diff --git a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java index 0094bfde..8c05d99b 100644 --- a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java +++ b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java @@ -23,9 +23,10 @@ public class SmartTrigger { - private static final String DURATION_PATTERN = - "(.+)(?:[&|]{2})(TargetDuration[<>=]+duration\\(['\"](\\d+[sSmMhH]+)['\"]\\))"; - private static final Pattern durationPattern = Pattern.compile(DURATION_PATTERN); + private static final String DURATION_PATTERN_STR = + "(TargetDuration[<>=]+duration\\(['\"](\\d+[sSmMhH]+)['\"]\\))"; + private static final String DEFINITION_PATTERN_STR = "(.+)(?:;)" + DURATION_PATTERN_STR; + private static final Pattern DEFINITION_PATTERN = Pattern.compile(DEFINITION_PATTERN_STR); public enum TriggerState { /* Newly Created or Condition not met. */ @@ -53,7 +54,7 @@ public SmartTrigger(String expression, String templateName) { this.expression = expression; this.recordingTemplate = templateName; this.state = TriggerState.NEW; - Matcher m = durationPattern.matcher(expression); + Matcher m = DEFINITION_PATTERN.matcher(expression); if (m.matches()) { triggerCondition = m.group(1); durationConstraint = m.group(2).replaceAll("'", "\""); From e97c8ccbc685be58af44b030a8e06f19e77cdce8 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 09:55:57 -0400 Subject: [PATCH 03/10] refactoring --- README.md | 2 +- .../cryostat/agent/triggers/SmartTrigger.java | 17 ++++--------- .../agent/triggers/TriggerEvaluator.java | 24 +++++++++++-------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 5e9961b4..c2a9d12f 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ converted into a `java.time.Duration` object representing the time threshold bef Smart Triggers may define more complex conditions that test multiple metrics: ``` -[(ProcessCpuLoad>0.5||SystemCpuLoad>0.25)&&HeapUsagePercent>0.1;TargetDuration>duration('1m')]~Continuous +[(ProcessCpuLoad>0.5||SystemCpuLoad>0.25)&&HeapMemoryUsagePercent>0.1;TargetDuration>duration('1m')]~Continuous ``` These may be passed as an argument to the Cryostat Agent, for example: diff --git a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java index 8c05d99b..280b8f6a 100644 --- a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java +++ b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java @@ -39,7 +39,7 @@ public enum TriggerState { COMPLETE }; - private final String expression; + private final String rawExpression; private final String durationConstraint; private final String triggerCondition; private final String recordingTemplate; @@ -51,7 +51,7 @@ public enum TriggerState { private volatile TriggerState state; public SmartTrigger(String expression, String templateName) { - this.expression = expression; + this.rawExpression = expression; this.recordingTemplate = templateName; this.state = TriggerState.NEW; Matcher m = DEFINITION_PATTERN.matcher(expression); @@ -68,7 +68,7 @@ public SmartTrigger(String expression, String templateName) { } public String getExpression() { - return expression; + return rawExpression; } public TriggerState getState() { @@ -110,11 +110,7 @@ public String getDurationConstraint() { @Override public int hashCode() { return Objects.hash( - expression, - durationConstraint, - triggerCondition, - recordingTemplate, - targetDuration); + durationConstraint, triggerCondition, recordingTemplate, targetDuration); } @Override @@ -129,8 +125,7 @@ public boolean equals(Object obj) { return false; } SmartTrigger other = (SmartTrigger) obj; - return Objects.equals(expression, other.expression) - && Objects.equals(durationConstraint, other.durationConstraint) + return Objects.equals(durationConstraint, other.durationConstraint) && Objects.equals(triggerCondition, other.triggerCondition) && Objects.equals(recordingTemplate, other.recordingTemplate) && Objects.equals(targetDuration, other.targetDuration); @@ -140,8 +135,6 @@ public boolean equals(Object obj) { public String toString() { return "SmartTrigger [durationConstraint=" + durationConstraint - + ", expression=" - + expression + ", recordingTemplate=" + recordingTemplate + ", targetDuration=" diff --git a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java index 8d379502..26c63f33 100644 --- a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java +++ b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java @@ -173,9 +173,11 @@ private void startRecording(SmartTrigger t) { tr.getRecording().start(); t.setState(TriggerState.COMPLETE); log.info( - "Started recording \"{}\" using template \"{}\"", + "Started recording \"{}\" using template \"{}\" due to trigger" + + " \"{}\"", recordingName, - t.getRecordingTemplateName()); + t.getRecordingTemplateName(), + t.getExpression()); }); } @@ -183,15 +185,17 @@ private boolean evaluateTriggerConstraint(SmartTrigger trigger, Duration targetD try { Map scriptVars = new HashMap<>(new MBeanInfo().getSimplifiedMetrics()); ScriptHost scriptHost = ScriptHost.newBuilder().build(); + StringBuilder condition = new StringBuilder(trigger.getTriggerCondition()); + if (!Duration.ZERO.equals(targetDuration)) { + condition.append("&&"); + condition.append(trigger.getDurationConstraint()); + scriptVars.put("TargetDuration", targetDuration); + } Script script = scriptHost - .buildScript( - Duration.ZERO.equals(targetDuration) - ? trigger.getTriggerCondition() - : trigger.getExpression()) + .buildScript(condition.toString()) .withDeclarations(buildDeclarations(scriptVars)) .build(); - scriptVars.put("TargetDuration", targetDuration); log.trace("evaluating mbean map:\n{}", scriptVars); Boolean result = script.execute(Boolean.class, scriptVars); return Boolean.TRUE.equals(result); @@ -209,17 +213,17 @@ private List buildDeclarations(Map scriptVars) { log.trace("Declaring script var {} [{}]", key, parseType); decls.add(Decls.newVar(key, parseType)); } - decls.add(Decls.newVar("TargetDuration", Decls.Duration)); return decls; } private Type parseType(Object obj) { if (obj.getClass().equals(String.class)) return Decls.String; - else if (obj.getClass().equals(Double.class)) return Decls.Double; - else if (obj.getClass().equals(Integer.class)) return Decls.Int; else if (obj.getClass().equals(Boolean.class)) return Decls.Bool; + else if (obj.getClass().equals(Integer.class)) return Decls.Int; else if (obj.getClass().equals(Long.class)) return Decls.newPrimitiveType(PrimitiveType.INT64); + else if (obj.getClass().equals(Double.class)) return Decls.Double; + else if (obj.getClass().equals(Duration.class)) return Decls.Duration; else // Default to String so we can still do some comparison return Decls.String; From e86e7a094e31d815935aa0992259396d1ad38391 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 10:12:51 -0400 Subject: [PATCH 04/10] cache generated scripts --- .../io/cryostat/agent/model/MBeanInfo.java | 7 +- .../agent/triggers/TriggerEvaluator.java | 76 +++++++++++++++---- 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/src/main/java/io/cryostat/agent/model/MBeanInfo.java b/src/main/java/io/cryostat/agent/model/MBeanInfo.java index 4a1b9195..c697ded3 100644 --- a/src/main/java/io/cryostat/agent/model/MBeanInfo.java +++ b/src/main/java/io/cryostat/agent/model/MBeanInfo.java @@ -45,12 +45,12 @@ public class MBeanInfo { private final Logger log = LoggerFactory.getLogger(getClass()); - private MBeanMetrics mBeanMetrics; - private Map simplifiedMetrics; + private final MBeanMetrics mBeanMetrics; + private final Map simplifiedMetrics; public MBeanInfo() { this.simplifiedMetrics = new HashMap<>(); - mBeanMetrics = + this.mBeanMetrics = new MBeanMetrics( getRuntimeMetrics(), getMemoryMetrics(), @@ -64,7 +64,6 @@ public MBeanMetrics getMBeanMetrics() { } public Map getSimplifiedMetrics() { - // Map simplifiedMetrics = new HashMap<>(); return Collections.unmodifiableMap(simplifiedMetrics); } diff --git a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java index 26c63f33..1110cdf8 100644 --- a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java +++ b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java @@ -19,9 +19,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; @@ -37,6 +37,7 @@ import com.google.api.expr.v1alpha1.Type.PrimitiveType; import org.projectnessie.cel.checker.Decls; import org.projectnessie.cel.tools.Script; +import org.projectnessie.cel.tools.ScriptCreateException; import org.projectnessie.cel.tools.ScriptHost; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +50,10 @@ public class TriggerEvaluator { private final FlightRecorderHelper flightRecorderHelper; private final Harvester harvester; private final long evaluationPeriodMs; + private final ConcurrentHashMap conditionScriptCache = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap durationScriptCache = + new ConcurrentHashMap<>(); private final ConcurrentLinkedQueue triggers = new ConcurrentLinkedQueue<>(); private Future task; private final Logger log = LoggerFactory.getLogger(getClass()); @@ -112,6 +117,8 @@ private void evaluate() { /* Trigger condition has been met, can remove it */ log.trace("Completed {} , removing", t); triggers.remove(t); + conditionScriptCache.remove(t); + durationScriptCache.remove(t); break; case NEW: // Simple Constraint, no duration specified so condition only needs to be @@ -183,28 +190,65 @@ private void startRecording(SmartTrigger t) { private boolean evaluateTriggerConstraint(SmartTrigger trigger, Duration targetDuration) { try { - Map scriptVars = new HashMap<>(new MBeanInfo().getSimplifiedMetrics()); - ScriptHost scriptHost = ScriptHost.newBuilder().build(); - StringBuilder condition = new StringBuilder(trigger.getTriggerCondition()); - if (!Duration.ZERO.equals(targetDuration)) { - condition.append("&&"); - condition.append(trigger.getDurationConstraint()); - scriptVars.put("TargetDuration", targetDuration); + Map conditionVars = new MBeanInfo().getSimplifiedMetrics(); + log.trace("evaluating mbean map:\n{}", conditionVars); + Boolean conditionResult = + buildConditionScript(trigger, conditionVars) + .execute(Boolean.class, conditionVars); + Boolean durationResult; + Map durationVar = Map.of("TargetDuration", targetDuration); + if (Duration.ZERO.equals(targetDuration)) { + durationResult = Boolean.TRUE; + } else { + durationResult = + buildDurationScript(trigger, durationVar) + .execute(Boolean.class, durationVar); } - Script script = - scriptHost - .buildScript(condition.toString()) - .withDeclarations(buildDeclarations(scriptVars)) - .build(); - log.trace("evaluating mbean map:\n{}", scriptVars); - Boolean result = script.execute(Boolean.class, scriptVars); - return Boolean.TRUE.equals(result); + return Boolean.TRUE.equals(conditionResult) && Boolean.TRUE.equals(durationResult); } catch (Exception e) { log.error("Failed to create or execute script", e); return false; } } + private Script buildConditionScript(SmartTrigger trigger, Map scriptVars) { + return conditionScriptCache.computeIfAbsent( + trigger, + t -> { + try { + ScriptHost scriptHost = ScriptHost.newBuilder().build(); + Script script = + scriptHost + .buildScript(trigger.getTriggerCondition()) + .withDeclarations(buildDeclarations(scriptVars)) + .build(); + return script; + } catch (ScriptCreateException sce) { + log.error("Failed to create condition script", sce); + throw new RuntimeException(sce); + } + }); + } + + private Script buildDurationScript(SmartTrigger trigger, Map scriptVars) { + return durationScriptCache.computeIfAbsent( + trigger, + t -> { + try { + ScriptHost scriptHost = ScriptHost.newBuilder().build(); + Script script = + scriptHost + .buildScript(trigger.getDurationConstraint()) + .withDeclarations(buildDeclarations(scriptVars)) + .build(); + return script; + } catch (ScriptCreateException sce) { + log.error("Failed to create duration script", sce); + throw new RuntimeException(sce); + } + }); + } + private List buildDeclarations(Map scriptVars) { ArrayList decls = new ArrayList<>(); for (Map.Entry s : scriptVars.entrySet()) { From d1b0ec431ed469bf196bc8f827ed5fed3f7e2c1b Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 10:18:30 -0400 Subject: [PATCH 05/10] more refactoring, use singleton scripthost --- .../agent/triggers/TriggerEvaluator.java | 46 +++++++------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java index 1110cdf8..81a2fccf 100644 --- a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java +++ b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java @@ -47,6 +47,7 @@ public class TriggerEvaluator { private final ScheduledExecutorService scheduler; private final List definitions; private final TriggerParser parser; + private final ScriptHost scriptHost; private final FlightRecorderHelper flightRecorderHelper; private final Harvester harvester; private final long evaluationPeriodMs; @@ -68,6 +69,7 @@ public TriggerEvaluator( this.scheduler = scheduler; this.definitions = Collections.unmodifiableList(definitions); this.parser = parser; + this.scriptHost = ScriptHost.newBuilder().build(); this.flightRecorderHelper = flightRecorderHelper; this.harvester = harvester; this.evaluationPeriodMs = evaluationPeriodMs; @@ -213,40 +215,24 @@ private boolean evaluateTriggerConstraint(SmartTrigger trigger, Duration targetD private Script buildConditionScript(SmartTrigger trigger, Map scriptVars) { return conditionScriptCache.computeIfAbsent( - trigger, - t -> { - try { - ScriptHost scriptHost = ScriptHost.newBuilder().build(); - Script script = - scriptHost - .buildScript(trigger.getTriggerCondition()) - .withDeclarations(buildDeclarations(scriptVars)) - .build(); - return script; - } catch (ScriptCreateException sce) { - log.error("Failed to create condition script", sce); - throw new RuntimeException(sce); - } - }); + trigger, t -> buildScript(t.getTriggerCondition(), scriptVars)); } private Script buildDurationScript(SmartTrigger trigger, Map scriptVars) { return durationScriptCache.computeIfAbsent( - trigger, - t -> { - try { - ScriptHost scriptHost = ScriptHost.newBuilder().build(); - Script script = - scriptHost - .buildScript(trigger.getDurationConstraint()) - .withDeclarations(buildDeclarations(scriptVars)) - .build(); - return script; - } catch (ScriptCreateException sce) { - log.error("Failed to create duration script", sce); - throw new RuntimeException(sce); - } - }); + trigger, t -> buildScript(t.getDurationConstraint(), scriptVars)); + } + + private Script buildScript(String script, Map scriptVars) { + try { + return scriptHost + .buildScript(script) + .withDeclarations(buildDeclarations(scriptVars)) + .build(); + } catch (ScriptCreateException sce) { + log.error("Failed to create script", sce); + throw new RuntimeException(sce); + } } private List buildDeclarations(Map scriptVars) { From 89ab9afc9f18154e116a0383859cffbe98b1f066 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 10:25:15 -0400 Subject: [PATCH 06/10] whitespace --- src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java index 81a2fccf..12f85533 100644 --- a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java +++ b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java @@ -197,6 +197,7 @@ private boolean evaluateTriggerConstraint(SmartTrigger trigger, Duration targetD Boolean conditionResult = buildConditionScript(trigger, conditionVars) .execute(Boolean.class, conditionVars); + Boolean durationResult; Map durationVar = Map.of("TargetDuration", targetDuration); if (Duration.ZERO.equals(targetDuration)) { @@ -206,6 +207,7 @@ private boolean evaluateTriggerConstraint(SmartTrigger trigger, Duration targetD buildDurationScript(trigger, durationVar) .execute(Boolean.class, durationVar); } + return Boolean.TRUE.equals(conditionResult) && Boolean.TRUE.equals(durationResult); } catch (Exception e) { log.error("Failed to create or execute script", e); From cabfea3fd5fc3263bd963fd496ac4aefc29bf5f1 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 10:26:00 -0400 Subject: [PATCH 07/10] refactor --- .../cryostat/agent/triggers/TriggerEvaluator.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java index 12f85533..8bc8c6e2 100644 --- a/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java +++ b/src/main/java/io/cryostat/agent/triggers/TriggerEvaluator.java @@ -198,15 +198,12 @@ private boolean evaluateTriggerConstraint(SmartTrigger trigger, Duration targetD buildConditionScript(trigger, conditionVars) .execute(Boolean.class, conditionVars); - Boolean durationResult; Map durationVar = Map.of("TargetDuration", targetDuration); - if (Duration.ZERO.equals(targetDuration)) { - durationResult = Boolean.TRUE; - } else { - durationResult = - buildDurationScript(trigger, durationVar) - .execute(Boolean.class, durationVar); - } + Boolean durationResult = + Duration.ZERO.equals(targetDuration) + ? Boolean.TRUE + : buildDurationScript(trigger, durationVar) + .execute(Boolean.class, durationVar); return Boolean.TRUE.equals(conditionResult) && Boolean.TRUE.equals(durationResult); } catch (Exception e) { From f6126835af77f1882810fd20405f3da12d6af706 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 10:35:17 -0400 Subject: [PATCH 08/10] update readme --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c2a9d12f..fb8ce840 100644 --- a/README.md +++ b/README.md @@ -40,16 +40,19 @@ shutdown so that the cause of an unexpected JVM shutdown might be captured for l ## SMART TRIGGERS -`cryostat-agent` supports smart triggers that listen to the values of the MBean Counters and can start recordings based +`cryostat-agent` supports Smart Triggers that listen to the values of the MBean Counters and can start recordings based on a set of constraints specified by the user. -The general form of a smart trigger expression is as follows: +The general form of a Smart Trigger expression is as follows: ``` -[constraint1(&&/||)constraint2...constraintN]~recordingTemplateNameOrLabel +[constraint1(&&/||/==)constraint2...constraintN;durationConstraint]~recordingTemplateNameOrLabel ``` -Either the filename or label XML tag of the `${templateName}.jfc` may be used to specify the event template to use. For example, the JDK distribution ships with a `default.jfc` file containing the top-level `` element. This template may be specified in the Smart Trigger definition as any of `default.jfc`, `default`, or `Continuous`. +Either the filename or label XML tag of the `${templateName}.jfc` may be used to specify the event template to use. For +example, the JDK distribution ships with a `default.jfc` file containing the top-level +`` element. This template may be specified in the Smart Trigger definition as any of +`default.jfc`, `default`, or `Continuous`. An example for listening to CPU Usage and starting a recording using the Profiling template when it exceeds 0.2%: @@ -65,11 +68,13 @@ Continuous template: ``` The first part of the condition before the semicolon is a [Common Expression Language](https://github.com/google/cel-spec) -expression for testing various MBean metrics. The second part after the semicolon references a special variable, -`TargetDuration`, which tracks the length of time that the first part of the condition has tested `true` for. This is -converted to a `java.time.Duration` object and compared to `duration("10s")`, a special construct that is also -converted into a `java.time.Duration` object representing the time threshold before this trigger activates. The -`duration()` construct requires a `String` argument, which may be enclosed in single `'` or double `"` quotation marks. +expression for testing +[various MBean metrics](https://github.com/cryostatio/cryostat-agent/blob/main/src/main/java/io/cryostat/agent/model/MBeanInfo.java) +. The second part after the semicolon references a special variable, `TargetDuration`, which tracks the length of time +that the first part of the condition has tested `true` for. This is converted to a `java.time.Duration` object and +compared to `duration("10s")`, a special construct that is also converted into a `java.time.Duration` object +representing the time threshold before this trigger activates. The `duration()` construct requires a `String` argument, +which may be enclosed in single `'` or double `"` quotation marks. Smart Triggers may define more complex conditions that test multiple metrics: From 36094c6f4fb92c207b7a0b7b8769ab9da938399d Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 14:51:35 -0400 Subject: [PATCH 09/10] readme fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb8ce840..cc134611 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ on a set of constraints specified by the user. The general form of a Smart Trigger expression is as follows: ``` -[constraint1(&&/||/==)constraint2...constraintN;durationConstraint]~recordingTemplateNameOrLabel +[constraint1(&&/||)constraint2...constraintN;durationConstraint]~recordingTemplateNameOrLabel ``` Either the filename or label XML tag of the `${templateName}.jfc` may be used to specify the event template to use. For From 60772bcad9c615d46ef56f5eb80d1ee083209414 Mon Sep 17 00:00:00 2001 From: Andrew Azores Date: Thu, 5 Oct 2023 16:07:29 -0400 Subject: [PATCH 10/10] re-add rawExpression to hashCode/equals/toString --- .../io/cryostat/agent/triggers/SmartTrigger.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java index 280b8f6a..5f2cef27 100644 --- a/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java +++ b/src/main/java/io/cryostat/agent/triggers/SmartTrigger.java @@ -110,7 +110,11 @@ public String getDurationConstraint() { @Override public int hashCode() { return Objects.hash( - durationConstraint, triggerCondition, recordingTemplate, targetDuration); + rawExpression, + durationConstraint, + triggerCondition, + recordingTemplate, + targetDuration); } @Override @@ -125,7 +129,8 @@ public boolean equals(Object obj) { return false; } SmartTrigger other = (SmartTrigger) obj; - return Objects.equals(durationConstraint, other.durationConstraint) + return Objects.equals(rawExpression, other.rawExpression) + && Objects.equals(durationConstraint, other.durationConstraint) && Objects.equals(triggerCondition, other.triggerCondition) && Objects.equals(recordingTemplate, other.recordingTemplate) && Objects.equals(targetDuration, other.targetDuration); @@ -133,7 +138,9 @@ public boolean equals(Object obj) { @Override public String toString() { - return "SmartTrigger [durationConstraint=" + return "SmartTrigger [rawExpression=" + + rawExpression + + ", durationConstraint=" + durationConstraint + ", recordingTemplate=" + recordingTemplate