getLabels() {
+ return labels;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ public String getNamespace() {
+ return namespace;
+ }
+
+ public String getHelp() {
+ return help;
+ }
+
+ public String getUnit() {
+ return unit;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final MetricDefinition that = (MetricDefinition) o;
+ return Objects.equals(name, that.name) && Objects.equals(labels, that.labels) && Objects.equals(type, that.type)
+ && Objects.equals(expression, that.expression) && Objects.equals(namespace, that.namespace)
+ && Objects.equals(help, that.help) && Objects.equals(unit, that.unit);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, labels, type, expression, namespace, help, unit);
+ }
+
+ @Override
+ public String toString() {
+ return "MetricDefinition{"
+ + "name='" + name + '\''
+ + ", tags=" + labels
+ + ", type='" + type + '\''
+ + ", expression='" + expression + '\''
+ + ", namespace='" + namespace + '\''
+ + ", help='" + help + '\''
+ + ", unit='" + unit + '\''
+ + '}';
+ }
+
+ /**
+ * This type is used to represent a label that is attached to a metric.
+ *
+ * Labels can have either a static value or an expression. If the value is an expression then this is evaluated as a watcher and the
+ * result is used as the label value.
+ */
+ public static class Label {
+
+ final String key;
+ final Object value;
+ final String expression;
+
+ /**
+ * Create a new label for a metric.
+ *
+ * @param key the label key
+ * @param value the label value if a fixed value
+ * @param expression the label expression if we should evaluate the label value
+ */
+ public Label(final String key, final Object value, final String expression) {
+ this.key = key;
+ this.value = value;
+ this.expression = expression;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ final Label label = (Label) o;
+ return Objects.equals(key, label.key) && Objects.equals(value, label.value) && Objects.equals(expression,
+ label.expression);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, value, expression);
+ }
+
+ @Override
+ public String toString() {
+ return "Label{"
+ + "key='" + key + '\''
+ + ", value=" + value
+ + ", expression='" + expression + '\''
+ + '}';
+ }
+ }
+}
diff --git a/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/MetricDefinitionTest.java b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/MetricDefinitionTest.java
new file mode 100644
index 0000000..fa39a2e
--- /dev/null
+++ b/agent-api/src/test/java/com/intergral/deep/agent/api/plugin/MetricDefinitionTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2023 Intergral GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.intergral.deep.agent.api.plugin;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.intergral.deep.agent.api.plugin.MetricDefinition.Label;
+import java.util.ArrayList;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+class MetricDefinitionTest {
+
+ private MetricDefinition metricDefinition;
+
+ @BeforeEach
+ void setUp() {
+ final ArrayList labels = new ArrayList<>();
+ labels.add(new Label("key", "value", null));
+ metricDefinition = new MetricDefinition("metric-name", labels, "metric-type", "metric-expression", "metric-namespace",
+ "metric-help", "metric-unit");
+ }
+
+ @Test
+ void getName() {
+ assertEquals("metric-name", metricDefinition.getName());
+ }
+
+ @Test
+ void getLabels() {
+ final ArrayList expectedLabels = new ArrayList<>();
+ expectedLabels.add(new Label("key", "value", null));
+ assertEquals(expectedLabels, metricDefinition.getLabels());
+ }
+
+ @Test
+ void getType() {
+ assertEquals("metric-type", metricDefinition.getType());
+ }
+
+ @Test
+ void getExpression() {
+ assertEquals("metric-expression", metricDefinition.getExpression());
+ }
+
+ @Test
+ void getNamespace() {
+ assertEquals("metric-namespace", metricDefinition.getNamespace());
+ }
+
+ @Test
+ void getHelp() {
+ assertEquals("metric-help", metricDefinition.getHelp());
+ }
+
+ @Test
+ void getUnit() {
+ assertEquals("metric-unit", metricDefinition.getUnit());
+ }
+
+ @Test
+ void equals() {
+ final ArrayList labels = new ArrayList<>();
+ labels.add(new Label("key", "value", null));
+ assertEquals(metricDefinition, new MetricDefinition("metric-name", labels, "metric-type", "metric-expression", "metric-namespace",
+ "metric-help", "metric-unit"));
+ assertNotSame(metricDefinition, new MetricDefinition("metric-name", labels, "metric-type", "metric-expression", "metric-namespace",
+ "metric-help", "metric-unit"));
+
+ assertEquals(metricDefinition.hashCode(),
+ new MetricDefinition("metric-name", labels, "metric-type", "metric-expression", "metric-namespace",
+ "metric-help", "metric-unit").hashCode());
+
+ assertEquals(metricDefinition.toString(),
+ new MetricDefinition("metric-name", labels, "metric-type", "metric-expression", "metric-namespace",
+ "metric-help", "metric-unit").toString());
+ }
+
+ @Nested
+ class LabelTest {
+
+ private Label label;
+
+ @BeforeEach
+ void setUp() {
+ label = new Label("key", "value", null);
+ }
+
+ @Test
+ void getKey() {
+ assertEquals("key", label.getKey());
+ }
+
+ @Test
+ void getValue() {
+ assertEquals("value", label.getValue());
+ }
+
+ @Test
+ void getExpression() {
+ assertNull(label.getExpression());
+ }
+ }
+}
\ No newline at end of file
diff --git a/agent/pom.xml b/agent/pom.xml
index b29fc47..2a1e730 100644
--- a/agent/pom.xml
+++ b/agent/pom.xml
@@ -77,6 +77,12 @@
1.0-SNAPSHOT
compile
+
+ com.intergral.deep.plugins
+ prometheus-metrics
+ 1.0-SNAPSHOT
+ provided
+
diff --git a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java
index 3cbbc37..304ef04 100644
--- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java
+++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java
@@ -19,6 +19,7 @@
import com.intergral.deep.agent.api.DeepVersion;
import com.intergral.deep.agent.api.IDeep;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.api.spi.IDeepPlugin;
import com.intergral.deep.agent.api.tracepoint.ITracepoint;
@@ -85,13 +86,13 @@ public String getVersion() {
@Override
public ITracepointRegistration registerTracepoint(final String path, final int line) {
- return registerTracepoint(path, line, Collections.emptyMap(), Collections.emptyList());
+ return registerTracepoint(path, line, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList());
}
@Override
public ITracepointRegistration registerTracepoint(final String path, final int line, final Map args,
- final Collection watches) {
- final TracePointConfig tracePointConfig = this.tracepointConfig.addCustom(path, line, args, watches);
+ final Collection watches, final Collection metricDefinitions) {
+ final TracePointConfig tracePointConfig = this.tracepointConfig.addCustom(path, line, args, watches, metricDefinitions);
return new ITracepointRegistration() {
@Override
public void unregister() {
diff --git a/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java b/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java
index abc5539..f227801 100644
--- a/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java
+++ b/agent/src/main/java/com/intergral/deep/agent/grpc/GrpcService.java
@@ -20,7 +20,6 @@
import com.intergral.deep.agent.api.DeepRuntimeException;
import com.intergral.deep.agent.api.auth.IAuthProvider;
import com.intergral.deep.agent.api.settings.ISettings;
-import com.intergral.deep.agent.api.spi.IDeepPlugin;
import com.intergral.deep.agent.settings.Settings;
import com.intergral.deep.proto.poll.v1.PollConfigGrpc;
import com.intergral.deep.proto.tracepoint.v1.SnapshotServiceGrpc;
@@ -37,7 +36,6 @@
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.ChannelOption;
import io.netty.channel.PreferHeapByteBufAllocator;
-import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executors;
@@ -185,18 +183,11 @@ private IAuthProvider loadAuth() {
return new NoopProvider();
}
- final Collection plugins = this.settings.getPlugins();
- for (IDeepPlugin plugin : plugins) {
- if (plugin.getClass().getName().equals(settingAs)) {
- if (plugin instanceof IAuthProvider) {
- return (IAuthProvider) plugin;
- } else {
- throw new DeepRuntimeException(
- String.format("Cannot use plugin %s as auth provider, must implement IAuthProvider interface.", plugin.getClass().getName()));
- }
- }
+ final IAuthProvider provider = this.settings.getPluginByName(IAuthProvider.class, settingAs);
+ if (provider == null) {
+ throw new DeepRuntimeException(String.format("Cannot load auth provider %s", settingAs));
}
- throw new DeepRuntimeException(String.format("Cannot load auth provider %s", settingAs));
+ return provider;
}
static class NoopProvider implements IAuthProvider {
diff --git a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java
index 1a23e2c..2e462ef 100644
--- a/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java
+++ b/agent/src/main/java/com/intergral/deep/agent/poll/LongPollService.java
@@ -17,6 +17,8 @@
package com.intergral.deep.agent.poll;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
+import com.intergral.deep.agent.api.plugin.MetricDefinition.Label;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.grpc.GrpcService;
import com.intergral.deep.agent.settings.Settings;
@@ -28,6 +30,8 @@
import com.intergral.deep.proto.poll.v1.PollRequest;
import com.intergral.deep.proto.poll.v1.PollResponse;
import com.intergral.deep.proto.poll.v1.ResponseType;
+import com.intergral.deep.proto.tracepoint.v1.LabelExpression;
+import com.intergral.deep.proto.tracepoint.v1.Metric;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -38,6 +42,7 @@
* This service deals with polling the remote service for tracepoint configs.
*/
public class LongPollService implements ITimerTask {
+
private final Settings settings;
private final GrpcService grpcService;
private final DriftAwareThread thread;
@@ -106,10 +111,65 @@ private Collection convertResponse(
tracePointConfig.getPath(),
tracePointConfig.getLineNumber(),
Collections.unmodifiableMap(new HashMap<>(tracePointConfig.getArgsMap())),
- Collections.unmodifiableCollection(tracePointConfig.getWatchesList())))
+ Collections.unmodifiableCollection(tracePointConfig.getWatchesList()),
+ Collections.unmodifiableList(covertMetrics(tracePointConfig.getMetricsList()))))
+ .collect(Collectors.toList());
+ }
+
+ List covertMetrics(final List metricsList) {
+ if (metricsList == null || metricsList.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return metricsList.stream().map(
+ metric -> new MetricDefinition(metric.getName(), convertLabels(metric.getLabelExpressionsList()), metric.getType().toString(),
+ metric.getExpression(),
+ namespaceOrDefault(metric.getNamespace()), helpOrDefault(metric.getHelp(), metric.getExpression()), metric.getUnit()))
.collect(Collectors.toList());
}
+ private List convertLabels(final List labelsList) {
+ return labelsList.stream().map(labelExpression ->
+ new Label(labelExpression.getKey(), labelExpression.hasStatic() ? convertAnyValue(labelExpression.getStatic()) : null,
+ labelExpression.hasExpression() ? labelExpression.getExpression() : null)
+ ).collect(Collectors.toList());
+ }
+
+ private Object convertAnyValue(final AnyValue staticValue) {
+ switch (staticValue.getValueCase()) {
+ case INT_VALUE:
+ return staticValue.getIntValue();
+ case BOOL_VALUE:
+ return staticValue.getBoolValue();
+ case ARRAY_VALUE:
+ return staticValue.getArrayValue();
+ case BYTES_VALUE:
+ return staticValue.getBytesValue();
+ case DOUBLE_VALUE:
+ return staticValue.getDoubleValue();
+ case KVLIST_VALUE:
+ return staticValue.getKvlistValue();
+ case STRING_VALUE:
+ return staticValue.getStringValue();
+ case VALUE_NOT_SET:
+ default:
+ return null;
+ }
+ }
+
+ private String namespaceOrDefault(final String namespace) {
+ if (namespace == null || namespace.trim().isEmpty()) {
+ return "deep_agent";
+ }
+ return namespace;
+ }
+
+ private String helpOrDefault(final String help, final String expression) {
+ if (help == null || help.trim().isEmpty()) {
+ return "Metric generated from expression: " + expression;
+ }
+ return help;
+ }
+
private com.intergral.deep.proto.resource.v1.Resource buildResource() {
final Resource resource = this.settings.getResource();
return com.intergral.deep.proto.resource.v1.Resource.newBuilder()
diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushService.java b/agent/src/main/java/com/intergral/deep/agent/push/PushService.java
index a075c6d..ecfb34d 100644
--- a/agent/src/main/java/com/intergral/deep/agent/push/PushService.java
+++ b/agent/src/main/java/com/intergral/deep/agent/push/PushService.java
@@ -20,7 +20,6 @@
import com.intergral.deep.agent.api.plugin.ISnapshotContext;
import com.intergral.deep.agent.api.plugin.ISnapshotDecorator;
import com.intergral.deep.agent.api.resource.Resource;
-import com.intergral.deep.agent.api.spi.IDeepPlugin;
import com.intergral.deep.agent.grpc.GrpcService;
import com.intergral.deep.agent.settings.Settings;
import com.intergral.deep.agent.types.snapshot.EventSnapshot;
@@ -59,17 +58,15 @@ public void pushSnapshot(final EventSnapshot snapshot, final ISnapshotContext co
}
private void decorate(final EventSnapshot snapshot, final ISnapshotContext context) {
- final Collection plugins = this.settings.getPlugins();
- for (IDeepPlugin plugin : plugins) {
- if (plugin instanceof ISnapshotDecorator) {
- try {
- final Resource decorate = ((ISnapshotDecorator) plugin).decorate(this.settings, context);
- if (decorate != null) {
- snapshot.mergeAttributes(decorate);
- }
- } catch (Throwable t) {
- LOGGER.error("Error processing plugin {}", plugin.getClass().getName());
+ final Collection plugins = this.settings.getPlugins(ISnapshotDecorator.class);
+ for (ISnapshotDecorator plugin : plugins) {
+ try {
+ final Resource decorate = plugin.decorate(this.settings, context);
+ if (decorate != null) {
+ snapshot.mergeAttributes(decorate);
}
+ } catch (Throwable t) {
+ LOGGER.error("Error processing plugin {}", plugin.getClass().getName());
}
}
snapshot.close();
diff --git a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java
index 1d6de45..b98649d 100644
--- a/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java
+++ b/agent/src/main/java/com/intergral/deep/agent/push/PushUtils.java
@@ -30,6 +30,7 @@
import com.intergral.deep.proto.tracepoint.v1.TracePointConfig;
import com.intergral.deep.proto.tracepoint.v1.Variable;
import com.intergral.deep.proto.tracepoint.v1.WatchResult;
+import com.intergral.deep.proto.tracepoint.v1.WatchSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
@@ -103,6 +104,7 @@ private static Iterable extends WatchResult> convertWatches(
return watches.stream().map(watchResult -> {
final WatchResult.Builder builder = WatchResult.newBuilder();
builder.setExpression(watchResult.expression());
+ builder.setSource(WatchSource.valueOf(watchResult.getSource()));
if (watchResult.isError()) {
builder.setErrorResult(watchResult.error());
} else {
diff --git a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java
index 21ec3db..c026dfa 100644
--- a/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java
+++ b/agent/src/main/java/com/intergral/deep/agent/settings/Settings.java
@@ -37,13 +37,17 @@
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
import java.util.logging.Level;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* A service that handles the general config of the deep agent.
*/
public class Settings implements ISettings {
+
private static final AtomicBoolean IS_ACTIVE = new AtomicBoolean(true);
private final Properties properties;
private Resource resource;
@@ -308,6 +312,22 @@ public List getAsList(final String key) {
return settingAs;
}
+ /**
+ * Get the plugin that is on the provided type and has the given name.
+ *
+ * @param clazz the type of the plugin to match
+ * @param name the class name of the plugin to match
+ * @param the type of the plugin
+ * @return the discovered plugin with the given name, or {@code null} if a plugin with the provided name and type cannot be found.
+ */
+ public T getPluginByName(final Class clazz, final String name) {
+ final Collection plugins = getPlugins(clazz, t -> t.getClass().getName().endsWith(name));
+ if (plugins.isEmpty()) {
+ return null;
+ }
+ return plugins.iterator().next();
+ }
+
/**
* Get all configured plugins.
*
@@ -317,6 +337,27 @@ public Collection getPlugins() {
return new ArrayList<>(this.plugins);
}
+ /**
+ * Get all plugins of the provided type.
+ *
+ * @param target the type of plugin we want
+ * @param the type of the plugin
+ * @return the list of plugins
+ */
+ public Collection getPlugins(final Class target) {
+ return getPlugins(target, null);
+ }
+
+ private Collection getPlugins(final Class target, final Predicate predicate) {
+ //noinspection unchecked
+ final Stream iDeepPluginStream = this.plugins.stream().filter(plugin -> target.isAssignableFrom(plugin.getClass()))
+ .map(plugin -> (T) plugin);
+ if (predicate != null) {
+ return iDeepPluginStream.filter(predicate).collect(Collectors.toList());
+ }
+ return iDeepPluginStream.collect(Collectors.toList());
+ }
+
/**
* Set configured plugins.
*
diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java
index 70ac34b..c62d3c0 100644
--- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java
+++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/TracepointConfigService.java
@@ -17,6 +17,7 @@
package com.intergral.deep.agent.tracepoint;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService;
import com.intergral.deep.agent.types.TracePointConfig;
import java.util.ArrayList;
@@ -86,12 +87,14 @@ public Collection loadTracepointConfigs(final Collection args,
- final Collection watches) {
- final TracePointConfig tracePointConfig = new TracePointConfig(UUID.randomUUID().toString(), path, line, args, watches);
+ final Collection watches, final Collection metrics) {
+ final TracePointConfig tracePointConfig = new TracePointConfig(UUID.randomUUID().toString(), path, line, args, watches,
+ metrics);
this.customTracepoints.add(tracePointConfig);
this.processChange();
return tracePointConfig;
diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java
index f0d8c90..e77d929 100644
--- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java
+++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameCollector.java
@@ -359,7 +359,7 @@ protected String getMethodName(final StackTraceElement stackTraceElement,
* @param watch the watch expression to evaluate
* @return a {@link IExpressionResult}
*/
- protected IExpressionResult evaluateWatchExpression(final String watch) {
+ protected IExpressionResult evaluateWatchExpression(final String watch, final String source) {
try {
final Object result = this.evaluator.evaluateExpression(watch, this.variables);
final List variableIds = processVars(Collections.singletonMap(watch, result));
@@ -367,7 +367,7 @@ protected IExpressionResult evaluateWatchExpression(final String watch) {
return new IExpressionResult() {
@Override
public WatchResult result() {
- return new WatchResult(watch, variableIds.get(0));
+ return new WatchResult(watch, variableIds.get(0), source);
}
@Override
@@ -379,13 +379,32 @@ public Map variables() {
public String logString() {
return Utils.valueOf(result);
}
+
+ @Override
+ public Number numberValue() {
+ if (result instanceof Number) {
+ // todo should we wrap this is new Number() ?
+ return (Number) result;
+ }
+ final String string = Utils.valueOf(result);
+ try {
+ return Double.valueOf(string);
+ } catch (NumberFormatException nfe) {
+ return Double.NaN;
+ }
+ }
+
+ @Override
+ public boolean isError() {
+ return false;
+ }
};
} catch (Throwable t) {
return new IExpressionResult() {
@Override
public WatchResult result() {
return new WatchResult(watch,
- String.format("%s: %s", t.getClass().getName(), t.getMessage()));
+ String.format("%s: %s", t.getClass().getName(), t.getMessage()), source);
}
@Override
@@ -397,6 +416,16 @@ public Map variables() {
public String logString() {
return Utils.throwableToString(t);
}
+
+ @Override
+ public Number numberValue() {
+ return Double.NaN;
+ }
+
+ @Override
+ public boolean isError() {
+ return true;
+ }
};
}
}
@@ -464,7 +493,7 @@ public Map variables() {
private String processSubstitution(final String logMsg, final ArrayList watchResults,
final HashMap variables) {
final StringSubstitutor stringSubstitutor = new StringSubstitutor(key -> {
- final IExpressionResult iExpressionResult = evaluateWatchExpression(key);
+ final IExpressionResult iExpressionResult = evaluateWatchExpression(key, WatchResult.LOG);
watchResults.add(iExpressionResult.result());
variables.putAll(iExpressionResult.variables());
return iExpressionResult.logString();
@@ -506,7 +535,7 @@ protected interface IFrameResult {
/**
* The result of evaluating an expression.
*
- * @see #evaluateWatchExpression(String)
+ * @see #evaluateWatchExpression(String, String)
*/
protected interface IExpressionResult {
@@ -515,6 +544,10 @@ protected interface IExpressionResult {
Map variables();
String logString();
+
+ Number numberValue();
+
+ boolean isError();
}
/**
diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java
index b2720e5..ec95f07 100644
--- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java
+++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessor.java
@@ -21,23 +21,33 @@
import com.intergral.deep.agent.Utils;
import com.intergral.deep.agent.api.plugin.EvaluationException;
import com.intergral.deep.agent.api.plugin.IEvaluator;
+import com.intergral.deep.agent.api.plugin.IMetricProcessor;
import com.intergral.deep.agent.api.plugin.ISnapshotContext;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
+import com.intergral.deep.agent.api.plugin.MetricDefinition.Label;
import com.intergral.deep.agent.api.reflection.IReflection;
import com.intergral.deep.agent.api.resource.Resource;
+import com.intergral.deep.agent.grpc.GrpcService;
import com.intergral.deep.agent.settings.Settings;
import com.intergral.deep.agent.types.TracePointConfig;
import com.intergral.deep.agent.types.snapshot.EventSnapshot;
import com.intergral.deep.agent.types.snapshot.WatchResult;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* This type deals with matching tracepoints to the current state and working out if we can collect the data.
*/
public class FrameProcessor extends FrameCollector implements ISnapshotContext {
+ private static final Logger LOGGER = LoggerFactory.getLogger(GrpcService.class);
+
/**
* The tracepoints that have been triggered by the Callback.
*/
@@ -124,7 +134,7 @@ public void configureSelf() {
public Collection collect() {
final Collection snapshots = new ArrayList<>();
- final IFrameResult processedFrame = super.processFrame();
+ final IFrameResult processedFrame = processFrame();
for (final TracePointConfig tracepoint : filteredTracepoints) {
try {
@@ -135,7 +145,7 @@ public Collection collect() {
processedFrame.variables());
for (String watch : tracepoint.getWatches()) {
- final FrameCollector.IExpressionResult result = super.evaluateWatchExpression(watch);
+ final FrameCollector.IExpressionResult result = evaluateWatchExpression(watch, WatchResult.WATCH);
snapshot.addWatchResult(result.result());
snapshot.mergeVariables(result.variables());
}
@@ -151,7 +161,18 @@ public Collection collect() {
this.logTracepoint(result.processedLog(), tracepoint.getId(), snapshot.getID());
}
- final Resource attributes = super.processAttributes(tracepoint);
+ final Collection metricDefinitions = tracepoint.getMetricDefinitions();
+ if (!metricDefinitions.isEmpty()) {
+ for (MetricDefinition metricDefinition : metricDefinitions) {
+ final List watchResults = processMetric(tracepoint, metricDefinition);
+ for (FrameCollector.IExpressionResult watchResult : watchResults) {
+ snapshot.addWatchResult(watchResult.result());
+ snapshot.mergeVariables(watchResult.variables());
+ }
+ }
+ }
+
+ final Resource attributes = processAttributes(tracepoint);
snapshot.mergeAttributes(attributes);
snapshots.add(snapshot);
@@ -164,6 +185,76 @@ public Collection collect() {
return snapshots;
}
+ List processMetric(final TracePointConfig tracepoint, final MetricDefinition metricDefinition) {
+ final List watchResults = new ArrayList<>();
+ final Collection metricProcessors = this.settings.getPlugins(IMetricProcessor.class);
+ if (metricProcessors.isEmpty()) {
+ return watchResults;
+ }
+
+ final Number value;
+ if (!metricDefinition.getExpression().trim().isEmpty()) {
+ final IExpressionResult iExpressionResult = evaluateWatchExpression(metricDefinition.getExpression(), WatchResult.METRIC);
+ watchResults.add(iExpressionResult);
+ value = iExpressionResult.numberValue();
+ if (iExpressionResult.isError() || Double.isNaN(value.doubleValue())) {
+ // the result of the expression is an error or not a number, so we skip for now
+ return watchResults;
+ }
+ } else {
+ // if there is no expression than default the value to 1
+ value = 1d;
+ }
+
+ final HashMap processedTags = new HashMap<>();
+ final List labels = metricDefinition.getLabels();
+ for (Label label : labels) {
+ final String expression = label.getExpression();
+ if (expression != null) {
+ final IExpressionResult tagResult = evaluateWatchExpression(expression, WatchResult.METRIC);
+ watchResults.add(tagResult);
+
+ processedTags.put(label.getKey(), tagResult.logString());
+ } else {
+ processedTags.put(label.getKey(), label.getValue());
+ }
+ }
+
+ for (IMetricProcessor metricProcessor : metricProcessors) {
+ try {
+ switch (metricDefinition.getType()) {
+ case "GAUGE":
+ metricProcessor.gauge(metricDefinition.getName(), processedTags, metricDefinition.getNamespace(), metricDefinition.getHelp(),
+ metricDefinition.getUnit(),
+ value.doubleValue());
+ break;
+ case "COUNTER":
+ metricProcessor.counter(metricDefinition.getName(), processedTags, metricDefinition.getNamespace(), metricDefinition.getHelp(),
+ metricDefinition.getUnit(),
+ value.doubleValue());
+ break;
+ case "HISTOGRAM":
+ metricProcessor.histogram(metricDefinition.getName(), processedTags, metricDefinition.getNamespace(),
+ metricDefinition.getHelp(), metricDefinition.getUnit(),
+ value.doubleValue());
+ break;
+ case "SUMMARY":
+ metricProcessor.summary(metricDefinition.getName(), processedTags, metricDefinition.getNamespace(), metricDefinition.getHelp(),
+ metricDefinition.getUnit(),
+ value.doubleValue());
+ break;
+ default:
+ LOGGER.error("Unsupported metric type: {}", metricDefinition.getType());
+ break;
+ }
+
+ } catch (Throwable t) {
+ LOGGER.error("Cannot process metric via {}", metricProcessor.getClass(), t);
+ }
+ }
+ return watchResults;
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPMappedBreakpoint.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPMappedBreakpoint.java
index 17708a0..c0d4357 100644
--- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPMappedBreakpoint.java
+++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/inst/jsp/JSPMappedBreakpoint.java
@@ -28,7 +28,7 @@ public class JSPMappedBreakpoint extends TracePointConfig {
public JSPMappedBreakpoint(final TracePointConfig tp, final int mappedLine) {
- super(tp.getId(), tp.getPath(), tp.getLineNo(), tp.getArgs(), tp.getWatches());
+ super(tp.getId(), tp.getPath(), tp.getLineNo(), tp.getArgs(), tp.getWatches(), tp.getMetricDefinitions());
this.mappedLine = mappedLine;
}
diff --git a/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java b/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java
index 39903cd..2ccc4b1 100644
--- a/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java
+++ b/agent/src/main/java/com/intergral/deep/agent/types/TracePointConfig.java
@@ -17,6 +17,7 @@
package com.intergral.deep.agent.types;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
import com.intergral.deep.agent.settings.Settings;
import java.util.Collection;
import java.util.Map;
@@ -84,25 +85,32 @@ public class TracePointConfig {
private final TracepointWindow window;
private final TracepointExecutionStats stats = new TracepointExecutionStats();
+ private final Collection metricDefinitions;
/**
* Create a new tracepoint config.
*
- * @param id the id
- * @param path the path
- * @param lineNo the line number
- * @param args the args
- * @param watches the watches
+ * @param id the id
+ * @param path the path
+ * @param lineNo the line number
+ * @param args the args
+ * @param watches the watches
+ * @param metricDefinitions the metrics to evaluate
*/
- public TracePointConfig(final String id, final String path, final int lineNo,
+ public TracePointConfig(
+ final String id,
+ final String path,
+ final int lineNo,
final Map args,
- final Collection watches) {
+ final Collection watches,
+ final Collection metricDefinitions) {
this.id = id;
this.path = path;
this.lineNo = lineNo;
this.args = args;
this.watches = watches;
+ this.metricDefinitions = metricDefinitions;
this.window = new TracepointWindow(this.getArg(WINDOW_START, Integer.class, 0),
this.getArg(WINDOW_END, Integer.class, 0));
}
@@ -170,6 +178,15 @@ public Collection getWatches() {
return watches;
}
+ /**
+ * Get the tracepoint metric definitions.
+ *
+ * @return the metric definitions
+ */
+ public Collection getMetricDefinitions() {
+ return metricDefinitions;
+ }
+
/**
* Get the tracepoint fire count.
*
diff --git a/agent/src/main/java/com/intergral/deep/agent/types/snapshot/WatchResult.java b/agent/src/main/java/com/intergral/deep/agent/types/snapshot/WatchResult.java
index a2d13b2..3d4e9fe 100644
--- a/agent/src/main/java/com/intergral/deep/agent/types/snapshot/WatchResult.java
+++ b/agent/src/main/java/com/intergral/deep/agent/types/snapshot/WatchResult.java
@@ -22,9 +22,31 @@
*/
public class WatchResult {
+ /**
+ * Watch source is METRIC.
+ *
+ * @see com.intergral.deep.proto.tracepoint.v1.WatchSource
+ */
+ public static final String METRIC = "METRIC";
+
+ /**
+ * Watch source is WATCH.
+ *
+ * @see com.intergral.deep.proto.tracepoint.v1.WatchSource
+ */
+ public static final String WATCH = "WATCH";
+
+ /**
+ * Watch source is LOG.
+ *
+ * @see com.intergral.deep.proto.tracepoint.v1.WatchSource
+ */
+ public static final String LOG = "LOG";
+
private final String error;
private final VariableID result;
private final String expression;
+ private final String source;
/**
@@ -32,11 +54,13 @@ public class WatchResult {
*
* @param expression the expression
* @param error the error
+ * @param source the source of this result
*/
- public WatchResult(final String expression, final String error) {
+ public WatchResult(final String expression, final String error, final String source) {
this.expression = expression;
this.error = error;
this.result = null;
+ this.source = source;
}
/**
@@ -44,9 +68,11 @@ public WatchResult(final String expression, final String error) {
*
* @param expression the expression
* @param result the result
+ * @param source the source of this result
*/
- public WatchResult(final String expression, final VariableID result) {
+ public WatchResult(final String expression, final VariableID result, final String source) {
this.expression = expression;
+ this.source = source;
this.error = null;
this.result = result;
}
@@ -66,4 +92,8 @@ public VariableID goodResult() {
public String expression() {
return this.expression;
}
+
+ public String getSource() {
+ return source;
+ }
}
diff --git a/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java
index fbfd7e3..7797ec1 100644
--- a/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java
+++ b/agent/src/test/java/com/intergral/deep/agent/poll/LongPollServiceTest.java
@@ -30,6 +30,8 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
+import com.intergral.deep.agent.api.plugin.MetricDefinition.Label;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.api.settings.ISettings;
import com.intergral.deep.agent.grpc.GrpcService;
@@ -41,19 +43,28 @@
import com.intergral.deep.proto.poll.v1.PollRequest;
import com.intergral.deep.proto.poll.v1.PollResponse;
import com.intergral.deep.proto.poll.v1.ResponseType;
+import com.intergral.deep.proto.tracepoint.v1.LabelExpression;
+import com.intergral.deep.proto.tracepoint.v1.Metric;
+import com.intergral.deep.proto.tracepoint.v1.MetricType;
import com.intergral.deep.proto.tracepoint.v1.TracePointConfig;
import com.intergral.deep.tests.grpc.TestPollService;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.net.ServerSocket;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
@@ -62,7 +73,7 @@ class LongPollServiceTest {
private Server server;
private LongPollService longPollService;
- private final AtomicReference request = new AtomicReference<>(null);
+ private final AtomicReference request = new AtomicReference<>(null);
private PollResponse response;
private GrpcService grpcService;
@@ -211,4 +222,43 @@ void doesSendResourceOnRequest() {
assertEquals("test", request.get().getResource().getAttributes(0).getKey());
assertEquals("resource", request.get().getResource().getAttributes(0).getValue().getStringValue());
}
+
+ @ParameterizedTest()
+ @MethodSource("canConvertMetricsSource")
+ void canConvertMetrics(final Metric input, final MetricDefinition expected) {
+ final List name = longPollService.covertMetrics(Collections.singletonList(input));
+
+ assertEquals(expected, name.iterator().next());
+ }
+
+ private static Stream canConvertMetricsSource() {
+ return Stream.of(
+ Arguments.of(Metric.newBuilder().setName("name").build(),
+ new MetricDefinition("name", new ArrayList<>(), "COUNTER", "", "deep_agent", "Metric generated from expression: ", "")),
+ Arguments.of(Metric.newBuilder().setName("name").setNamespace("custom").build(),
+ new MetricDefinition("name", new ArrayList<>(), "COUNTER", "", "custom", "Metric generated from expression: ", "")),
+ Arguments.of(Metric.newBuilder().setName("name").setNamespace("custom").setHelp("This is my metric").build(),
+ new MetricDefinition("name", new ArrayList<>(), "COUNTER", "", "custom", "This is my metric", "")),
+ Arguments.of(Metric.newBuilder().setName("name").setNamespace("custom").setExpression("this.cnt").build(),
+ new MetricDefinition("name", new ArrayList<>(), "COUNTER", "this.cnt", "custom", "Metric generated from expression: this.cnt",
+ "")),
+ Arguments.of(Metric.newBuilder().setUnit("unit").setName("name").setType(MetricType.GAUGE).setExpression("this.cnt").build(),
+ new MetricDefinition("name", new ArrayList<>(), "GAUGE", "this.cnt", "deep_agent", "Metric generated from expression: this.cnt",
+ "unit")),
+ Arguments.of(Metric.newBuilder().addLabelExpressions(
+ LabelExpression.newBuilder()
+ .setKey("key")
+ .setStatic(AnyValue.newBuilder().setStringValue("some string").build())
+ .build())
+ .setUnit("unit").setName("name").setType(MetricType.GAUGE).setExpression("this.cnt").build(),
+ new MetricDefinition("name", Collections.singletonList(new Label("key", "some string", null)), "GAUGE", "this.cnt",
+ "deep_agent", "Metric generated from expression: this.cnt",
+ "unit")),
+ Arguments.of(Metric.newBuilder().addLabelExpressions(LabelExpression.newBuilder().setKey("key").setExpression("some.thing").build())
+ .setUnit("unit").setName("name").setType(MetricType.GAUGE).setExpression("this.cnt").build(),
+ new MetricDefinition("name", Collections.singletonList(new Label("key", null, "some.thing")), "GAUGE", "this.cnt", "deep_agent",
+ "Metric generated from expression: this.cnt",
+ "unit"))
+ );
+ }
}
\ No newline at end of file
diff --git a/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java b/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java
index a5a0282..7a92cc8 100644
--- a/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java
+++ b/agent/src/test/java/com/intergral/deep/agent/settings/SettingsTest.java
@@ -20,13 +20,18 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.intergral.deep.agent.api.logger.ITracepointLogger;
+import com.intergral.deep.agent.api.plugin.IMetricProcessor;
+import com.intergral.deep.agent.api.spi.IDeepPlugin;
import com.intergral.deep.agent.settings.Settings.InvalidConfigException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -146,4 +151,61 @@ void tracepointLogger_can_log() {
settings.logTracepoint("log", "tp_id", "snap_id");
Mockito.verify(tracepointLogger, Mockito.times(1)).logTracepoint("log", "tp_id", "snap_id");
}
+
+ @Test
+ void plugins() {
+ final Settings settings = Settings.build(new HashMap<>());
+ final TestPlugin plugin = new TestPlugin();
+ settings.setPlugins(Collections.singleton(plugin));
+
+ final Collection plugins = settings.getPlugins();
+ assertEquals(1, plugins.size());
+ assertEquals(plugin, plugins.iterator().next());
+
+ final Collection loggers = settings.getPlugins(ITracepointLogger.class);
+ assertEquals(1, loggers.size());
+ assertEquals(plugin, loggers.iterator().next());
+
+ final ITracepointLogger pluginByName = settings.getPluginByName(ITracepointLogger.class, TestPlugin.class.getName());
+ assertNotNull(pluginByName);
+ assertSame(pluginByName, plugin);
+
+ final Collection notPlugin = settings.getPlugins(String.class);
+ assertTrue(notPlugin.isEmpty());
+
+ final ITracepointLogger notFound = settings.getPluginByName(ITracepointLogger.class, getClass().getName());
+ assertNull(notFound);
+ }
+
+ private static class TestPlugin implements IDeepPlugin, IMetricProcessor, ITracepointLogger {
+
+ @Override
+ public void counter(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+
+ }
+
+ @Override
+ public void gauge(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+
+ }
+
+ @Override
+ public void histogram(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+
+ }
+
+ @Override
+ public void summary(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+
+ }
+
+ @Override
+ public void logTracepoint(final String logMsg, final String tracepointId, final String snapshotId) {
+
+ }
+ }
}
\ No newline at end of file
diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java
index 16e0374..db96434 100644
--- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java
+++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/TracepointConfigServiceTest.java
@@ -58,7 +58,7 @@ void noChange() {
void customCallsUpdate() {
// add a custom tracepoint will call instrumentation update
final TracePointConfig tracePointConfig = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(),
- Collections.emptyList());
+ Collections.emptyList(), Collections.emptyList());
final ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class);
@@ -88,11 +88,11 @@ void customCallsUpdate() {
void canLoadTracepointConfigs() {
final TracePointConfig tracePointConfig1 = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(),
- Collections.emptyList());
+ Collections.emptyList(), Collections.emptyList());
final TracePointConfig tracePointConfig2 = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(),
- Collections.emptyList());
+ Collections.emptyList(), Collections.emptyList());
final TracePointConfig tracePointConfig3 = tracepointConfigService.addCustom("/path", 123, Collections.emptyMap(),
- Collections.emptyList());
+ Collections.emptyList(), Collections.emptyList());
{
final Collection tracePointConfigs = tracepointConfigService.loadTracepointConfigs(
@@ -121,7 +121,8 @@ void canLoadTracepointConfigs() {
void configUpdate() {
// TP config is passed to instrumentation
tracepointConfigService.configUpdate(10101, "hash",
- Collections.singletonList(new TracePointConfig("some-id", "path", 123, Collections.emptyMap(), Collections.emptyList())));
+ Collections.singletonList(
+ new TracePointConfig("some-id", "path", 123, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList())));
final ArgumentCaptor> captor = ArgumentCaptor.forClass(Collection.class);
@@ -135,7 +136,7 @@ void configUpdate() {
// custom and config are passed to instrumentation
final TracePointConfig customTp = tracepointConfigService.addCustom("path", 123, Collections.emptyMap(),
- Collections.emptyList());
+ Collections.emptyList(), Collections.emptyList());
Mockito.verify(instrumentationService, Mockito.times(2)).processBreakpoints(captor.capture());
@@ -149,7 +150,8 @@ void configUpdate() {
//custom remains after update
tracepointConfigService.configUpdate(10101, "hash",
- Collections.singletonList(new TracePointConfig("some-id", "path", 123, Collections.emptyMap(), Collections.emptyList())));
+ Collections.singletonList(
+ new TracePointConfig("some-id", "path", 123, Collections.emptyMap(), Collections.emptyList(), Collections.emptyList())));
Mockito.verify(instrumentationService, Mockito.times(3)).processBreakpoints(captor.capture());
diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java
index b94bd14..4bb7027 100644
--- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java
+++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameCollectorTest.java
@@ -18,6 +18,8 @@
package com.intergral.deep.agent.tracepoint.handler;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import com.intergral.deep.agent.api.plugin.IEvaluator;
import com.intergral.deep.agent.api.resource.Resource;
@@ -49,20 +51,26 @@ void setUp() {
@Test
void evaluateWatchers() throws Throwable {
Mockito.when(evaluator.evaluateExpression(Mockito.anyString(), Mockito.anyMap())).thenReturn("some result");
- final IExpressionResult someExpression = frameCollector.evaluateWatchExpression("some expression");
+ final IExpressionResult someExpression = frameCollector.evaluateWatchExpression("some expression", "test");
assertEquals("some expression", someExpression.result().expression());
assertEquals(1, someExpression.variables().size());
final Variable variable = someExpression.variables().get("1");
assertEquals("some result", variable.getValString());
+ assertFalse(someExpression.isError());
+ assertEquals(Double.NaN, someExpression.numberValue());
+ assertEquals("test", someExpression.result().getSource());
}
@Test
void evaluateWatchers_error() throws Throwable {
Mockito.when(evaluator.evaluateExpression(Mockito.anyString(), Mockito.anyMap())).thenThrow(new RuntimeException("Test exception"));
- final IExpressionResult someExpression = frameCollector.evaluateWatchExpression("some expression");
+ final IExpressionResult someExpression = frameCollector.evaluateWatchExpression("some expression", "test");
assertEquals("some expression", someExpression.result().expression());
assertEquals(0, someExpression.variables().size());
assertEquals("java.lang.RuntimeException: Test exception", someExpression.result().error());
+ assertTrue(someExpression.isError());
+ assertEquals(Double.NaN, someExpression.numberValue());
+ assertEquals("test", someExpression.result().getSource());
}
@Test
diff --git a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java
index 7981aca..ac96fdf 100644
--- a/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java
+++ b/agent/src/test/java/com/intergral/deep/agent/tracepoint/handler/FrameProcessorTest.java
@@ -25,6 +25,8 @@
import com.intergral.deep.agent.Utils;
import com.intergral.deep.agent.api.plugin.EvaluationException;
import com.intergral.deep.agent.api.plugin.IEvaluator;
+import com.intergral.deep.agent.api.plugin.IMetricProcessor;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.settings.Settings;
import com.intergral.deep.agent.tracepoint.evaluator.EvaluatorService;
@@ -36,6 +38,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -233,4 +236,23 @@ void willCollectLogMessage_with_watch_this_i() {
final String logMsg = next.getLogMsg();
assertEquals("[deep] some log message: 100", logMsg);
}
+
+ @Test
+ void processMetric() {
+ final MockTracepointConfig tracepointConfig = new MockTracepointConfig().withMetric();
+ tracepoints.add(tracepointConfig);
+ assertTrue(frameProcessor.canCollect());
+ frameProcessor.configureSelf();
+
+ final IMetricProcessor metricProcessor = Mockito.mock(IMetricProcessor.class);
+
+ Mockito.when(settings.getPlugins(IMetricProcessor.class)).thenReturn(Collections.singleton(metricProcessor));
+
+ final MetricDefinition metricDefinition = tracepointConfig.getMetricDefinitions().iterator().next();
+ frameProcessor.processMetric(tracepointConfig, metricDefinition);
+
+ Mockito.verify(metricProcessor, Mockito.times(1))
+ .counter(metricDefinition.getName(), new HashMap<>(), metricDefinition.getNamespace(), metricDefinition.getHelp(),
+ metricDefinition.getUnit(), 1.0d);
+ }
}
\ No newline at end of file
diff --git a/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java b/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java
index 139a219..ce6c890 100644
--- a/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java
+++ b/agent/src/test/java/com/intergral/deep/test/MockEventSnapshot.java
@@ -31,8 +31,8 @@
public class MockEventSnapshot extends EventSnapshot {
public MockEventSnapshot() {
- super(new TracePointConfig("tp-1", "/some/file/path.py", 123, new HashMap<>(), new ArrayList<>()), 1011L, Resource.DEFAULT,
- new ArrayList<>(), new HashMap<>());
+ super(new TracePointConfig("tp-1", "/some/file/path.py", 123, new HashMap<>(), new ArrayList<>(), new ArrayList<>()), 1011L,
+ Resource.DEFAULT, new ArrayList<>(), new HashMap<>());
}
public MockEventSnapshot withFrames() {
@@ -70,8 +70,8 @@ public MockEventSnapshot withAttributes() {
}
public MockEventSnapshot withWatches() {
- getWatches().add(new WatchResult("error", "this is an error"));
- getWatches().add(new WatchResult("good", new VariableID("some-var", "withName", new HashSet<>(), null)));
+ getWatches().add(new WatchResult("error", "this is an error", "LOG"));
+ getWatches().add(new WatchResult("good", new VariableID("some-var", "withName", new HashSet<>(), null), "LOG"));
return this;
}
diff --git a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java
index 5844623..a52bff2 100644
--- a/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java
+++ b/agent/src/test/java/com/intergral/deep/test/MockTracepointConfig.java
@@ -17,6 +17,7 @@
package com.intergral.deep.test;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
import com.intergral.deep.agent.types.TracePointConfig;
import java.util.ArrayList;
import java.util.Arrays;
@@ -28,16 +29,16 @@
public class MockTracepointConfig extends TracePointConfig {
public MockTracepointConfig() {
- super("tp-id", "path", 123, new HashMap<>(), new ArrayList<>());
+ super("tp-id", "path", 123, new HashMap<>(), new ArrayList<>(), new ArrayList<>());
}
public MockTracepointConfig(final String path) {
- super("tp-id", path, 123, new HashMap<>(), new ArrayList<>());
+ super("tp-id", path, 123, new HashMap<>(), new ArrayList<>(), new ArrayList<>());
}
public MockTracepointConfig(final String path, final int line) {
- super("tp-id", path, line, new HashMap<>(), new ArrayList<>());
+ super("tp-id", path, line, new HashMap<>(), new ArrayList<>(), new ArrayList<>());
}
public MockTracepointConfig withArg(final String key, final String value) {
@@ -49,4 +50,10 @@ public MockTracepointConfig withWatches(final String... watches) {
this.getWatches().addAll(Arrays.asList(watches));
return this;
}
+
+ public MockTracepointConfig withMetric() {
+ this.getMetricDefinitions()
+ .add(new MetricDefinition("name", new ArrayList<>(), "COUNTER", "", "deep_agent", "Metric generated from expression: ", "unit"));
+ return this;
+ }
}
diff --git a/deep/pom.xml b/deep/pom.xml
index 7bbf6a3..4c6a516 100644
--- a/deep/pom.xml
+++ b/deep/pom.xml
@@ -69,6 +69,12 @@
1.0-SNAPSHOT
compile
+
+ com.intergral.deep.plugins
+ prometheus-metrics
+ 1.0-SNAPSHOT
+ compile
+
@@ -127,27 +133,43 @@
+
${git.branch}
+
${git.commit.id}
+
${git.commit.time}
+
${git.dirty}
+
${git.tags}
+
${git.remote.origin.url}
${maven.build.timestamp}
+
${parsedVersion.majorVersion}
+
${parsedVersion.minorVersion}
+
${parsedVersion.incrementalVersion}
${project.version}
+
${git.commit.id}
+
${git.branch}
+
${env.CI_PROJECT_NAME}
+
${env.CI_PIPELINE_ID}
+
${env.CI_PIPELINE_IID}
+
${env.CI_PIPELINE_SOURCE}
+
${env.CI_PIPELINE_URL}
${project.artifactId}
diff --git a/deep/src/main/java/resources/META-INF/services/com.intergral.deep.agent.api.spi.IDeepPlugin b/deep/src/main/java/resources/META-INF/services/com.intergral.deep.agent.api.spi.IDeepPlugin
new file mode 100644
index 0000000..7f05f9c
--- /dev/null
+++ b/deep/src/main/java/resources/META-INF/services/com.intergral.deep.agent.api.spi.IDeepPlugin
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2023 Intergral GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+com.intergral.deep.plugin.PrometheusMetricsPlugin
diff --git a/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java b/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java
index 21e7e36..3b61601 100644
--- a/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java
+++ b/examples/agent-load/src/main/java/com/intergral/deep/examples/Main.java
@@ -48,7 +48,7 @@ public static void main(String[] args) throws Throwable {
// USe the API to create a tracepoint that will fire forever
DeepAPI.api()
.registerTracepoint("com/intergral/deep/examples/SimpleTest", 46, Collections.singletonMap("fire_count", "-1"),
- Collections.emptyList());
+ Collections.emptyList(), Collections.emptyList());
final SimpleTest ts = new SimpleTest("This is a test", 2);
//noinspection InfiniteLoopStatement
diff --git a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java
index ce74ddf..e36922e 100644
--- a/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java
+++ b/examples/dynamic-load/src/main/java/com/intergral/deep/examples/Main.java
@@ -73,7 +73,7 @@ public static void main(String[] args) throws Throwable {
// USe the API to create a tracepoint that will fire forever
DeepAPI.api()
.registerTracepoint("com/intergral/deep/examples/SimpleTest", 46,
- Collections.singletonMap("fire_count", "-1"), Collections.emptyList());
+ Collections.singletonMap("fire_count", "-1"), Collections.emptyList(), Collections.emptyList());
final SimpleTest ts = new SimpleTest("This is a test", 2);
//noinspection InfiniteLoopStatement
diff --git a/examples/pom.xml b/examples/pom.xml
index f309ab1..d483fcd 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -37,6 +37,7 @@
agent-load
dynamic-load
+ prometheus-metrics-example
diff --git a/examples/prometheus-metrics-example/pom.xml b/examples/prometheus-metrics-example/pom.xml
new file mode 100644
index 0000000..bbb081e
--- /dev/null
+++ b/examples/prometheus-metrics-example/pom.xml
@@ -0,0 +1,70 @@
+
+
+
+
+ 4.0.0
+
+ com.intergral.deep.examples
+ examples
+ 1.0-SNAPSHOT
+
+
+ prometheus-metrics-example
+
+
+
+
+
+
+
+ com.intergral.deep
+ deep
+ 1.0-SNAPSHOT
+ compile
+
+
+
+ com.intergral.deep
+ agent
+ 1.0-SNAPSHOT
+ provided
+
+
+ com.intergral.deep.plugins
+ prometheus-metrics
+ 1.0-SNAPSHOT
+
+
+
+
+ io.prometheus
+ prometheus-metrics-core
+ 1.1.0
+
+
+ io.prometheus
+ prometheus-metrics-exporter-httpserver
+ 1.1.0
+
+
+
+
\ No newline at end of file
diff --git a/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/BaseTest.java b/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/BaseTest.java
new file mode 100644
index 0000000..1a3ba44
--- /dev/null
+++ b/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/BaseTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 Intergral GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.intergral.deep.examples;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
+public class BaseTest {
+
+ protected final Properties systemProps = System.getProperties();
+
+
+ public String newId() {
+ return UUID.randomUUID().toString();
+ }
+
+
+ public Map makeCharCountMap(final String str) {
+ final HashMap res = new HashMap();
+
+ for (int i = 0; i < str.length(); i++) {
+ final char c = str.charAt(i);
+ final Integer cnt = res.get(c);
+ if (cnt == null) {
+ res.put(c, 0);
+ } else {
+ res.put(c, cnt + 1);
+ }
+ }
+
+ return res;
+ }
+}
diff --git a/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/Main.java b/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/Main.java
new file mode 100644
index 0000000..dbbf6cd
--- /dev/null
+++ b/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/Main.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2023 Intergral GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.intergral.deep.examples;
+
+
+import com.intergral.deep.Deep;
+import com.intergral.deep.agent.api.IDeep;
+import com.intergral.deep.agent.api.plugin.MetricDefinition;
+import com.intergral.deep.agent.api.reflection.IReflection;
+import com.intergral.deep.agent.api.settings.ISettings;
+import com.intergral.deep.api.DeepAPI;
+import io.prometheus.metrics.core.metrics.Histogram;
+import io.prometheus.metrics.exporter.httpserver.HTTPServer;
+import io.prometheus.metrics.model.snapshots.Unit;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * This example expects the deep agent to be loaded via the javaagent vm option.
+ *
+ * See RunConfigurations for IDEA:
+ *
+ * Dynamic Load without JavaAgent
+ * Dynamic Load with JavaAgent
+ *
+ */
+public class Main {
+
+ /**
+ * Main entry for example.
+ *
+ * @param args the startup arguments
+ * @throws Throwable if we error
+ */
+ public static void main(String[] args) throws Throwable {
+ // this is only needed in this example as we are using a local built module
+ // if using the dependency from maven you do not need to set the path
+ //noinspection DataFlowIssue
+ Path jarPath = Paths.get(Main.class.getResource("/").toURI())
+ .getParent()
+ .getParent()
+ .getParent()
+ .getParent()
+ .resolve("agent/target/agent-1.0-SNAPSHOT.jar");
+
+ // Dynamically configure and attach the deep agent
+ Deep.config()
+ .setJarPath(jarPath.toAbsolutePath().toString())
+ .setValue(ISettings.KEY_SERVICE_URL, "localhost:43315")
+ .setValue(ISettings.KEY_SERVICE_SECURE, false)
+ .start();
+
+ // different ways to get the API instance
+ final Deep instance = Deep.getInstance();
+ System.out.println(instance.api().getVersion());
+ System.out.println(instance.reflection());
+
+ System.out.println(DeepAPI.api().getVersion());
+ System.out.println(DeepAPI.reflection());
+
+ Histogram histogram = Histogram.builder()
+ .name("request_latency_seconds")
+ .help("request latency in seconds")
+ .unit(Unit.SECONDS)
+ .labelNames("path", "status")
+ .register();
+
+ try (HTTPServer server = HTTPServer.builder()
+ .port(9400)
+ .buildAndStart()) {
+
+ System.out.println("HTTPServer listening on port http://localhost:" + server.getPort() + "/metrics");
+
+ final HashMap tags = new HashMap<>();
+ // USe the API to create a tracepoint that will fire forever
+ final Map fireCount = new HashMap<>();
+ fireCount.put("fire_count", "-1");
+ fireCount.put("log_msg", "This is a log message {this}");
+
+ DeepAPI.api()
+ .registerTracepoint("com/intergral/deep/examples/SimpleTest", 46,
+ fireCount, Collections.emptyList(),
+ Collections.singletonList(
+ new MetricDefinition("custom_metric", tags, "histogram", "this.cnt", "deep", "help message", "unit")));
+
+ Random random = new Random(0);
+ final SimpleTest ts = new SimpleTest("This is a test", 2);
+ //noinspection InfiniteLoopStatement
+ for (; ; ) {
+ try {
+ ts.message(ts.newId());
+ } catch (Exception e) {
+ //noinspection CallToPrintStackTrace
+ e.printStackTrace();
+ }
+
+ double duration = Math.abs(random.nextGaussian() / 10.0 + 0.2);
+ String status = random.nextInt(100) < 20 ? "500" : "200";
+ histogram.labelValues("/", status).observe(duration);
+
+ //noinspection BusyWait
+ Thread.sleep(1000);
+ }
+ }
+ }
+}
diff --git a/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/SimpleTest.java b/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/SimpleTest.java
new file mode 100644
index 0000000..760235b
--- /dev/null
+++ b/examples/prometheus-metrics-example/src/main/java/com/intergral/deep/examples/SimpleTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 Intergral GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.intergral.deep.examples;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class SimpleTest extends BaseTest {
+
+ public static Date NICE_START_DATE = new Date();
+
+ private final long startedAt = System.currentTimeMillis();
+ private final String testName;
+ private final int maxExecutions;
+ public int cnt = 0;
+ private Map charCounter = new TreeMap();
+
+
+ public SimpleTest(final String testName, final int maxExecutions) {
+ this.testName = testName;
+ this.maxExecutions = maxExecutions;
+ }
+
+
+ void message(final String uuid) throws Exception {
+ System.out.println(cnt + ":" + uuid);
+ cnt += 1;
+
+ checkEnd(cnt, maxExecutions);
+
+ final Map info = makeCharCountMap(uuid);
+ merge(charCounter, info);
+ if ((cnt % 30) == 0) {
+ dump();
+ }
+ }
+
+
+ void merge(final Map charCounter, final Map newInfo) {
+ for (final Character c : newInfo.keySet()) {
+ final Integer i = newInfo.get(c);
+
+ Integer curr = charCounter.get(c);
+ if (curr == null) {
+ charCounter.put(c, i);
+ } else {
+ charCounter.put(c, curr + i);
+ }
+ }
+ }
+
+
+ void dump() {
+ System.out.println(charCounter);
+ charCounter = new HashMap();
+ }
+
+
+ static void checkEnd(final int val, final int max) throws Exception {
+ if (val > max) {
+ throw new Exception("Hit max executions " + val + " " + max);
+ }
+ }
+
+
+ @Override
+ public String toString() {
+ return getClass().getName() + ":" + testName + ":" + startedAt + "#" + System.identityHashCode(
+ this);
+ }
+}
diff --git a/plugins/pom.xml b/plugins/pom.xml
index 1607ccc..47f43ab 100644
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -30,6 +30,7 @@
java-plugin
cf-plugin
+ prometheus-metrics
com.intergral.deep.plugins
diff --git a/plugins/prometheus-metrics/pom.xml b/plugins/prometheus-metrics/pom.xml
new file mode 100644
index 0000000..78b37ee
--- /dev/null
+++ b/plugins/prometheus-metrics/pom.xml
@@ -0,0 +1,50 @@
+
+
+
+
+ 4.0.0
+
+ com.intergral.deep.plugins
+ plugins
+ 1.0-SNAPSHOT
+
+
+ prometheus-metrics
+
+
+ 1.1.0
+
+
+
+
+ com.intergral.deep
+ agent-api
+ 1.0-SNAPSHOT
+ compile
+
+
+ io.prometheus
+ prometheus-metrics-core
+ ${prometheus.version}
+ provided
+
+
+
+
\ No newline at end of file
diff --git a/plugins/prometheus-metrics/src/main/java/com/intergral/deep/plugin/PrometheusMetricsPlugin.java b/plugins/prometheus-metrics/src/main/java/com/intergral/deep/plugin/PrometheusMetricsPlugin.java
new file mode 100644
index 0000000..7132f01
--- /dev/null
+++ b/plugins/prometheus-metrics/src/main/java/com/intergral/deep/plugin/PrometheusMetricsPlugin.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 Intergral GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.intergral.deep.plugin;
+
+import com.intergral.deep.agent.api.plugin.IMetricProcessor;
+import com.intergral.deep.agent.api.spi.IConditional;
+import com.intergral.deep.agent.api.spi.IDeepPlugin;
+import com.intergral.deep.agent.api.spi.Ordered;
+import io.prometheus.metrics.core.metrics.Counter;
+import io.prometheus.metrics.core.metrics.Counter.Builder;
+import io.prometheus.metrics.core.metrics.Gauge;
+import io.prometheus.metrics.core.metrics.Histogram;
+import io.prometheus.metrics.core.metrics.Summary;
+import io.prometheus.metrics.model.registry.PrometheusRegistry;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This plugin provides the ability to post tracepoint generated metric to prometheus.
+ *
+ * This plugin is loaded by the Deep module and not the agent. As we need to be loaded in the app class path and not the boot class path.
+ */
+public class PrometheusMetricsPlugin implements IDeepPlugin, IConditional, IMetricProcessor, Ordered {
+
+ private static final Map REGISTRY_CACHE = new ConcurrentHashMap<>();
+
+ @Override
+ public boolean isActive() {
+ try {
+ // just trying to see if the prometheus classes are loaded
+ final PrometheusRegistry ignored = PrometheusRegistry.defaultRegistry;
+ return true;
+ } catch (Throwable t) {
+ return false;
+ }
+ }
+
+ @Override
+ public void counter(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+ final String ident = buildIdent("counter", namespace, name, labels.keySet());
+ Object o = REGISTRY_CACHE.get(ident);
+ if (o == null) {
+
+ final Builder builder = Counter.builder().name(String.format("%s_%s", namespace, name)).help(help);
+ if (!labels.isEmpty()) {
+ builder.labelNames(Arrays.toString(labels.keySet().toArray()));
+ }
+ final Counter register = builder.register();
+ REGISTRY_CACHE.put(ident, register);
+
+ o = register;
+ }
+
+ if (!labels.isEmpty()) {
+ ((Counter) o).labelValues(Arrays.toString(labels.values().toArray())).inc(value);
+ } else {
+ ((Counter) o).inc(value);
+ }
+ }
+
+ @Override
+ public void gauge(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+ final String ident = buildIdent("gauge", namespace, name, labels.keySet());
+ Object o = REGISTRY_CACHE.get(ident);
+ if (o == null) {
+ final Gauge.Builder builder = Gauge.builder().name(String.format("%s_%s", namespace, name)).help(help);
+ if (!labels.isEmpty()) {
+ builder.labelNames(Arrays.toString(labels.keySet().toArray()));
+ }
+ final Gauge register = builder.register();
+ REGISTRY_CACHE.put(ident, register);
+
+ o = register;
+ }
+
+ if (!labels.isEmpty()) {
+ ((Gauge) o).labelValues(Arrays.toString(labels.values().toArray())).set(value);
+ } else {
+ ((Gauge) o).set(value);
+ }
+ }
+
+ @Override
+ public void histogram(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+ final String ident = buildIdent("histogram", namespace, name, labels.keySet());
+ Object o = REGISTRY_CACHE.get(ident);
+ if (o == null) {
+ final Histogram.Builder builder = Histogram.builder().name(String.format("%s_%s", namespace, name)).help(help);
+ if (!labels.isEmpty()) {
+ builder.labelNames(Arrays.toString(labels.keySet().toArray()));
+ }
+ final Histogram register = builder.register();
+ REGISTRY_CACHE.put(ident, register);
+ o = register;
+ }
+
+ if (!labels.isEmpty()) {
+ ((Histogram) o).labelValues(Arrays.toString(labels.values().toArray())).observe(value);
+ } else {
+ ((Histogram) o).observe(value);
+ }
+ }
+
+ @Override
+ public void summary(final String name, final Map labels, final String namespace, final String help, final String unit,
+ final Double value) {
+ final String ident = buildIdent("summary", namespace, name, labels.keySet());
+ Object o = REGISTRY_CACHE.get(ident);
+ if (o == null) {
+ final Summary.Builder builder = Summary.builder().name(String.format("%s_%s", namespace, name)).help(help);
+ if (!labels.isEmpty()) {
+ builder.labelNames(Arrays.toString(labels.keySet().toArray()));
+ }
+ final Summary register = builder.register();
+ REGISTRY_CACHE.put(ident, register);
+ o = register;
+ }
+
+ if (!labels.isEmpty()) {
+ ((Summary) o).labelValues(Arrays.toString(labels.values().toArray())).observe(value);
+ } else {
+ ((Summary) o).observe(value);
+ }
+ }
+
+ private String buildIdent(final String type, final String namespace, final String name, final Set labelNames) {
+ return String.format("%s_%s_%s_%s", type, namespace, name, String.join("-", labelNames));
+ }
+}
diff --git a/pom.xml b/pom.xml
index 2ea5919..6ccde73 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,7 @@
+ 1.0.3
1.60.0
4.1.98.Final
2.0.62.Final
@@ -452,7 +453,7 @@
com.intergral.deep
deep-proto
- 1.0.2
+ ${deep-proto.version}