diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java b/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java
index d3ab29b..389ad71 100644
--- a/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java
+++ b/agent-api/src/main/java/com/intergral/deep/agent/api/IDeep.java
@@ -17,7 +17,52 @@
package com.intergral.deep.agent.api;
+import com.intergral.deep.agent.api.plugin.IPlugin;
+import com.intergral.deep.agent.api.plugin.IPlugin.IPluginRegistration;
+import com.intergral.deep.agent.api.tracepoint.ITracepoint.ITracepointRegistration;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * This type describes the main API for Deep.
+ *
+ * This API can only be used after the agent has been loaded.
+ */
public interface IDeep {
+ /**
+ * Get the version of deep being used.
+ *
+ * @return the sematic version of deep as a string e.g. 1.2.3
+ */
String getVersion();
+
+ /**
+ * This allows the registration of custom plugins.
+ *
+ * @param plugin the plugin that can be used to decorate snapshots
+ * @return a {@link IPluginRegistration} that can be used to unregister the plugin
+ */
+ IPluginRegistration registerPlugin(final IPlugin plugin);
+
+ /**
+ * Create a tracepoint that will only exist on this instance.
+ *
+ * @param path the path to the file
+ * @param line the line number
+ * @return a {@link ITracepointRegistration} that can be used to remove the tracepoint
+ */
+ ITracepointRegistration registerTracepoint(final String path, final int line);
+
+ /**
+ * Create a tracepoint that will only exist on this instance.
+ *
+ * @param path the path to the file
+ * @param line the line number
+ * @param args the key value pairs that further define the tracepoint
+ * @param watches the list of watch expressions
+ * @return a {@link ITracepointRegistration} that can be used to remove the tracepoint
+ */
+ ITracepointRegistration registerTracepoint(final String path, final int line, final Map args,
+ final Collection watches);
}
diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEventContext.java b/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java
similarity index 78%
rename from agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEventContext.java
rename to agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java
index 63d4944..973d1da 100644
--- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IEventContext.java
+++ b/agent-api/src/main/java/com/intergral/deep/agent/api/IRegistration.java
@@ -15,9 +15,15 @@
* along with this program. If not, see .
*/
-package com.intergral.deep.agent.api.plugin;
+package com.intergral.deep.agent.api;
-public interface IEventContext {
+/**
+ * This is a generic interface from the result of a registration.
+ */
+public interface IRegistration {
- String evaluateExpression(String expression) throws Throwable;
+ /**
+ * Unregister the item registered.
+ */
+ void unregister();
}
diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java
new file mode 100644
index 0000000..ccb22b1
--- /dev/null
+++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * This exception is thrown when a plugin tried to evaluate an expression that fails.
+ */
+public class EvaluationException extends Exception {
+
+ /**
+ * The expression that was evaluated.
+ */
+ private final String expression;
+
+ /**
+ * Create a new exception
+ *
+ * @param expression the expression that failed
+ * @param cause the failure
+ */
+ public EvaluationException(final String expression, final Throwable cause) {
+ super("Could not evaluate expression: " + expression, cause);
+ this.expression = expression;
+ }
+
+ /**
+ * Get the expression
+ *
+ * @return {@link #expression}
+ */
+ public String getExpression() {
+ return expression;
+ }
+}
diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java
index a80ccdb..b0fd9a7 100644
--- a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java
+++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/IPlugin.java
@@ -17,15 +17,48 @@
package com.intergral.deep.agent.api.plugin;
+import com.intergral.deep.agent.api.IRegistration;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.api.settings.ISettings;
+/**
+ * This type is used to define a Deep Plugin.
+ *
+ * Plugins can decorate the snapshot when they are captured allowing for additional metadata to be attached. e.g. OTEL span data.
+ */
public interface IPlugin {
- Resource decorate(final ISettings settings, final IEventContext snapshot);
+ /**
+ * This method is called by Deep after a snapshot is created.
+ *
+ * This method is executed inline with the tracepoint code.
+ *
+ * @param settings the current settings of Deep
+ * @param snapshot the {@link ISnapshotContext} describing the snapshot
+ * @return a new {@link Resource} to be added to the snapshot, or {@code null} to do nothing
+ */
+ Resource decorate(final ISettings settings, final ISnapshotContext snapshot);
+
+ /**
+ * The name of the plugin. This should be unique.
+ *
+ * @return the plugin name, defaults to the full class name
+ */
+ default String name() {
+ return this.getClass().getName();
+ }
+ /**
+ * Is this plugin active.
+ *
+ * By default, this will check the Deep settings for the plugin name. e.g. the setting com.intergral.deep.plugin.JavaPlugin.active=false
+ * will disable the JavaPlugin. The normal settings rules apply, e.g. deep. or DEEP_ as a prefix when using system properties or environment variables.
+ *
+ * @param settings the current deep settings.
+ * @return {@code false} if setting is 'false', otherwise {@code true}
+ */
default boolean isActive(final ISettings settings) {
- final String simpleName = this.getClass().getSimpleName();
+ final String simpleName = this.name();
final Boolean settingAs = settings.getSettingAs(String.format("%s.active", simpleName),
Boolean.class);
if (settingAs == null) {
@@ -33,4 +66,11 @@ default boolean isActive(final ISettings settings) {
}
return settingAs;
}
+
+ /**
+ * This type describes a registered plugin.
+ */
+ interface IPluginRegistration extends IRegistration {
+
+ }
}
diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java
new file mode 100644
index 0000000..ba1f694
--- /dev/null
+++ b/agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * This is the context passed to plugins. This allows for the data of a context to be exposed to the plugin in a controlled manor.
+ */
+public interface ISnapshotContext {
+
+ /**
+ * Evaluate an expression in the frame of the tracepoint that triggered this snapshot
+ *
+ * @param expression the express to evaluate
+ * @return the result of the expression as a string
+ * @throws EvaluationException if there were any issues evaluating the expression
+ */
+ String evaluateExpression(String expression) throws EvaluationException;
+}
diff --git a/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java
new file mode 100644
index 0000000..de79c61
--- /dev/null
+++ b/agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java
@@ -0,0 +1,47 @@
+/*
+ * 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.tracepoint;
+
+import com.intergral.deep.agent.api.IRegistration;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.UUID;
+
+public interface ITracepoint {
+
+ String path();
+
+ int line();
+
+ default Map args() {
+ return Collections.emptyMap();
+ }
+
+ default Collection watches() {
+ return Collections.emptyList();
+ }
+
+ default String id() {
+ return UUID.randomUUID().toString();
+ }
+
+ interface ITracepointRegistration extends IRegistration {
+
+ }
+}
diff --git a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java
index c0a9191..91d6358 100644
--- a/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java
+++ b/agent/src/main/java/com/intergral/deep/agent/AgentImpl.java
@@ -17,7 +17,6 @@
package com.intergral.deep.agent;
-import com.intergral.deep.agent.api.DeepVersion;
import com.intergral.deep.agent.api.IDeep;
import com.intergral.deep.agent.api.hook.IDeepHook;
import com.intergral.deep.agent.api.reflection.IReflection;
@@ -26,9 +25,13 @@
import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService;
import java.lang.instrument.Instrumentation;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
public class AgentImpl {
+ private static final CountDownLatch LATCH = new CountDownLatch(1);
+ private static DeepAgent deepAgent;
+
public static void startup(final Instrumentation inst, final Map args) {
final Settings settings = Settings.build(args);
Logger.configureLogging(settings);
@@ -36,22 +39,28 @@ public static void startup(final Instrumentation inst, final Map
final TracepointInstrumentationService tracepointInstrumentationService =
TracepointInstrumentationService.init(inst, settings);
- final DeepAgent deepAgent = new DeepAgent(settings, tracepointInstrumentationService);
+ final DeepAgent deep = new DeepAgent(settings, tracepointInstrumentationService);
+
+ deep.start();
+
+ deepAgent = deep;
+ LATCH.countDown();
+ }
- deepAgent.start();
+ public static Object awaitLoadAPI() throws InterruptedException {
+ LATCH.await();
+ return loadDeepAPI();
}
public static Object loadDeepAPI() {
+ if (deepAgent == null) {
+ throw new IllegalStateException("Must start DEEP first");
+ }
return new IDeepHook() {
@Override
public IDeep deepService() {
- return new IDeep() {
- @Override
- public String getVersion() {
- return DeepVersion.VERSION;
- }
- };
+ return deepAgent;
}
@Override
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 ab808a1..273e0a9 100644
--- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java
+++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java
@@ -17,8 +17,12 @@
package com.intergral.deep.agent;
+import com.intergral.deep.agent.api.DeepVersion;
+import com.intergral.deep.agent.api.IDeep;
import com.intergral.deep.agent.api.plugin.IPlugin;
+import com.intergral.deep.agent.api.plugin.IPlugin.IPluginRegistration;
import com.intergral.deep.agent.api.resource.Resource;
+import com.intergral.deep.agent.api.tracepoint.ITracepoint.ITracepointRegistration;
import com.intergral.deep.agent.grpc.GrpcService;
import com.intergral.deep.agent.plugins.PluginLoader;
import com.intergral.deep.agent.poll.LongPollService;
@@ -28,9 +32,13 @@
import com.intergral.deep.agent.tracepoint.TracepointConfigService;
import com.intergral.deep.agent.tracepoint.handler.Callback;
import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService;
+import com.intergral.deep.agent.types.TracePointConfig;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
-public class DeepAgent {
+public class DeepAgent implements IDeep {
private final Settings settings;
private final GrpcService grpcService;
@@ -58,4 +66,26 @@ public void start() {
this.grpcService.start();
this.pollService.start(tracepointConfig);
}
+
+ @Override
+ public String getVersion() {
+ return DeepVersion.VERSION;
+ }
+
+ public IPluginRegistration registerPlugin(final IPlugin plugin) {
+ this.settings.addPlugin(plugin);
+ return () -> this.settings.removePlugin(plugin);
+ }
+
+ @Override
+ public ITracepointRegistration registerTracepoint(final String path, final int line) {
+ return registerTracepoint(path, line, Collections.emptyMap(), 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);
+ return () -> this.tracepointConfig.removeCustom(tracePointConfig);
+ }
}
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 52eb4cb..525a265 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
@@ -17,7 +17,7 @@
package com.intergral.deep.agent.push;
-import com.intergral.deep.agent.api.plugin.IEventContext;
+import com.intergral.deep.agent.api.plugin.ISnapshotContext;
import com.intergral.deep.agent.api.plugin.IPlugin;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.grpc.GrpcService;
@@ -42,7 +42,7 @@ public PushService(final Settings settings, final GrpcService grpcService) {
this.grpcService = grpcService;
}
- public void pushSnapshot(final EventSnapshot snapshot, final IEventContext context) {
+ public void pushSnapshot(final EventSnapshot snapshot, final ISnapshotContext context) {
decorate(snapshot, context);
final Snapshot grpcSnapshot = PushUtils.convertToGrpc(snapshot);
this.grpcService.snapshotService().send(grpcSnapshot, new StreamObserver() {
@@ -63,7 +63,7 @@ public void onCompleted() {
});
}
- private void decorate(final EventSnapshot snapshot, final IEventContext context) {
+ private void decorate(final EventSnapshot snapshot, final ISnapshotContext context) {
final Collection plugins = this.settings.getPlugins();
for (IPlugin plugin : plugins) {
try {
@@ -71,7 +71,7 @@ private void decorate(final EventSnapshot snapshot, final IEventContext context)
if (decorate != null) {
snapshot.mergeAttributes(decorate);
}
- } catch (Exception e) {
+ } catch (Throwable t) {
LOGGER.error("Error processing plugin {}", plugin.getClass().getName());
}
}
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 c4c81e6..ca35824 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
@@ -26,12 +26,14 @@
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Properties;
import java.util.logging.Level;
import java.util.regex.Pattern;
@@ -41,6 +43,7 @@ public class Settings implements ISettings {
private final Properties properties;
private Resource resource;
private Collection plugins;
+ private final Collection customPlugins = new ArrayList<>();
private Settings(Properties properties) {
this.properties = properties;
@@ -250,13 +253,27 @@ public List getAsList(final String key) {
}
public Collection getPlugins() {
- return this.plugins;
+ final ArrayList actualPlugins = new ArrayList<>(this.plugins);
+ actualPlugins.addAll(this.customPlugins);
+ return actualPlugins;
}
public void setPlugins(Collection plugins) {
this.plugins = plugins;
}
+ public void addPlugin(final IPlugin plugin) {
+ final Optional first = this.customPlugins.stream().filter(iPlugin -> iPlugin.name().equals(plugin.name())).findFirst();
+ if (first.isPresent()) {
+ throw new IllegalStateException(String.format("Cannot add duplicate named (%s) plugin", plugin.name()));
+ }
+ this.customPlugins.add(plugin);
+ }
+
+ public void removePlugin(final IPlugin plugin) {
+ this.plugins.removeIf(iPlugin -> iPlugin.name().equals(plugin.name()));
+ }
+
public static class InvalidConfigException extends RuntimeException {
public InvalidConfigException(String key, String value) {
diff --git a/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java b/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java
index 4dd1a0e..5e2603b 100644
--- a/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java
+++ b/agent/src/main/java/com/intergral/deep/agent/tracepoint/ITracepointConfig.java
@@ -24,8 +24,7 @@ public interface ITracepointConfig {
void noChange(final long tsNano);
- void configUpdate(final long tsNano, final String hash,
- final Collection tracepoints);
+ void configUpdate(final long tsNano, final String hash, final Collection tracepoints);
String currentHash();
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 af46f64..38ff7fb 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
@@ -19,8 +19,13 @@
import com.intergral.deep.agent.tracepoint.inst.TracepointInstrumentationService;
import com.intergral.deep.agent.types.TracePointConfig;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -29,7 +34,8 @@ public class TracepointConfigService implements ITracepointConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(TracepointConfigService.class);
private final TracepointInstrumentationService tracepointInstrumentationService;
private String currentHash = null;
- private Collection installedTracepoints;
+ private final Collection customTracepoints = new ArrayList<>();
+ private Collection installedTracepoints = new ArrayList<>();
@SuppressWarnings("unused")
private long lastUpdate;
@@ -49,7 +55,13 @@ public void configUpdate(long tsNano, String hash, Collection
this.currentHash = hash;
this.installedTracepoints = tracepoints;
this.lastUpdate = tsNano;
- this.tracepointInstrumentationService.processBreakpoints(tracepoints);
+ processChange();
+ }
+
+ private void processChange() {
+ final List allTracepoints = Stream.concat(this.installedTracepoints.stream(), this.customTracepoints.stream())
+ .collect(Collectors.toList());
+ this.tracepointInstrumentationService.processBreakpoints(allTracepoints);
}
@Override
@@ -59,8 +71,20 @@ public String currentHash() {
@Override
public Collection loadTracepointConfigs(final Collection tracepointId) {
- return installedTracepoints.stream()
+ return Stream.concat(installedTracepoints.stream(), customTracepoints.stream())
.filter(tracePointConfig -> tracepointId.contains(tracePointConfig.getId()))
.collect(Collectors.toList());
}
+
+ public TracePointConfig addCustom(final String path, final int line, final Map args,
+ final Collection watches) {
+ final TracePointConfig tracePointConfig = new TracePointConfig(UUID.randomUUID().toString(), path, line, args, watches);
+ this.customTracepoints.add(tracePointConfig);
+ this.processChange();
+ return tracePointConfig;
+ }
+
+ public void removeCustom(final TracePointConfig tracePointConfig) {
+ this.customTracepoints.removeIf(current -> current.getId().equals(tracePointConfig.getId()));
+ }
}
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 e476bc4..d9636c6 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
@@ -18,8 +18,9 @@
package com.intergral.deep.agent.tracepoint.handler;
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.IEventContext;
+import com.intergral.deep.agent.api.plugin.ISnapshotContext;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.settings.Settings;
import com.intergral.deep.agent.types.TracePointConfig;
@@ -29,7 +30,7 @@
import java.util.Map;
import java.util.stream.Collectors;
-public class FrameProcessor extends FrameCollector implements IEventContext {
+public class FrameProcessor extends FrameCollector implements ISnapshotContext {
private final Collection tracePointConfigs;
private final long[] lineStart;
@@ -115,9 +116,13 @@ protected T getTracepointConfig(final String key, final Class clazz, T de
}
@Override
- public String evaluateExpression(final String expression) throws Throwable {
- final Object o = this.evaluator.evaluateExpression(expression, this.variables);
- return Utils.valueOf(o);
+ public String evaluateExpression(final String expression) throws EvaluationException {
+ try {
+ final Object o = this.evaluator.evaluateExpression(expression, this.variables);
+ return Utils.valueOf(o);
+ } catch (Throwable t) {
+ throw new EvaluationException(expression, t);
+ }
}
public interface IFactory {
diff --git a/deep/src/main/java/com/intergral/deep/DEEPAPI.java b/deep/src/main/java/com/intergral/deep/DEEPAPI.java
new file mode 100644
index 0000000..f827771
--- /dev/null
+++ b/deep/src/main/java/com/intergral/deep/DEEPAPI.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import com.intergral.deep.agent.api.IDeep;
+import com.intergral.deep.agent.api.reflection.IReflection;
+
+/**
+ * This type provides helper methods to get the api and other exposed APIs from deep. This type MUST not be used until after the agent is
+ * loaded or there will be Exceptions thrown.
+ */
+public class DEEPAPI {
+
+ /**
+ * Get the reflection API
+ *
+ * @return a {@link IReflection} instance for the java version we are running
+ */
+ public static IReflection reflection() {
+ return Deep.getInstance().reflection();
+ }
+
+ /**
+ * Get the Deep api
+ *
+ * @return a {@link IDeep} instance
+ */
+ public static IDeep api() {
+ return Deep.getInstance().api();
+ }
+}
diff --git a/deep/src/main/java/com/intergral/deep/Deep.java b/deep/src/main/java/com/intergral/deep/Deep.java
index 16dfe70..c07361f 100644
--- a/deep/src/main/java/com/intergral/deep/Deep.java
+++ b/deep/src/main/java/com/intergral/deep/Deep.java
@@ -97,7 +97,7 @@ private void startDeep(final String config, final String jarPath) {
try {
loadAgent(config, jarPath);
- loadAPI();
+ awaitAPI();
} catch (Throwable t) {
t.printStackTrace();
}
@@ -144,6 +144,36 @@ IDeepLoader getLoader() throws Throwable {
return (IDeepLoader) newInstance;
}
+ /**
+ * When we start Deep via the config, (ie not as a javaagent) we need to await the start. This should not take very long.
+ *
+ * Essentially when we tell Bytebuddy to load the agent, this is sometimes done as an async external process. Which we cannot await. We
+ * need to, however, get an instance of the deep api hooked back after deep has connected. So we just keep trying.
+ *
+ * This should not ever fail, as the agent will either load, or we will get an exception from the load. Once it is loaded however it can
+ * take some time to initialise depending on the size of the environment.
+ */
+ private void awaitAPI() {
+ if (this.deepService != null) {
+ // api already loaded
+ return;
+ }
+ while (this.deepService == null) {
+ try {
+ // this will throw a Deep not started error, if deep has not started.
+ loadAPI();
+ } catch (Exception ignored) {
+ try {
+ //noinspection BusyWait
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+
private void loadAPI() {
if (this.deepService != null) {
// api already loaded
@@ -151,7 +181,9 @@ private void loadAPI() {
}
try {
- final Class> aClass = Class.forName("com.intergral.deep.agent.AgentImpl");
+ // we need to use the system class loader here, as when we run in OSGi or other complex environments. The class loader we have at
+ // this point might be isolated and wont be able to load the agent class.
+ final Class> aClass = ClassLoader.getSystemClassLoader().loadClass("com.intergral.deep.agent.AgentImpl");
final Method registerBreakpointService = aClass.getDeclaredMethod("loadDeepAPI");
final Object invoke = registerBreakpointService.invoke(null);
if (invoke != null) {
diff --git a/deep/src/main/java/com/intergral/deep/DeepLoader.java b/deep/src/main/java/com/intergral/deep/DeepLoader.java
index 23a2e99..1403839 100644
--- a/deep/src/main/java/com/intergral/deep/DeepLoader.java
+++ b/deep/src/main/java/com/intergral/deep/DeepLoader.java
@@ -19,8 +19,6 @@
import com.intergral.deep.api.IDeepLoader;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -61,7 +59,11 @@ private File getToolsJar() {
* @return the {@link File} object for the agent
*/
private File getAgentJar(final String jarPath) {
- final InputStream resourceAsStream = getAgentJarStream(jarPath);
+ final File file = new File(jarPath);
+ if (file.exists()) {
+ return file;
+ }
+ final InputStream resourceAsStream = getAgentJarStream();
final String pathname = extractLibrary(resourceAsStream);
if (pathname != null) {
return new File(pathname);
@@ -75,18 +77,7 @@ private File getAgentJar(final String jarPath) {
*
* @return the stream to use, or {@code null}
*/
- private InputStream getAgentJarStream(final String jarPath) {
- // this is pretty much just for testing, see Example
- final String property = System.getProperty("deep.jar.path", jarPath);
- if (property != null) {
- try {
- return new FileInputStream(property);
- } catch (FileNotFoundException e) {
- System.err.println("Unable to load Deep jar from path: " + property);
- e.printStackTrace(System.err);
- return null;
- }
- }
+ private InputStream getAgentJarStream() {
return Deep.class.getResourceAsStream("/deep-agent.jar");
}
diff --git a/examples/agent-load/pom.xml b/examples/agent-load/pom.xml
index 3353736..9e4c9fd 100644
--- a/examples/agent-load/pom.xml
+++ b/examples/agent-load/pom.xml
@@ -39,4 +39,11 @@
UTF-8
+
+
+ com.intergral.deep
+ deep
+ 1.0-SNAPSHOT
+
+
\ No newline at end of file
diff --git a/examples/agent-load/src/main/java/com/intergral/deep/examples/BaseTest.java b/examples/agent-load/src/main/java/com/intergral/deep/examples/BaseTest.java
index 6046cf6..1a3ba44 100644
--- a/examples/agent-load/src/main/java/com/intergral/deep/examples/BaseTest.java
+++ b/examples/agent-load/src/main/java/com/intergral/deep/examples/BaseTest.java
@@ -1,17 +1,18 @@
/*
- * Copyright 2023 Intergral GmbH
+ * Copyright (C) 2023 Intergral GmbH
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * 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;
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 2193d07..8c933bb 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
@@ -1,25 +1,58 @@
/*
- * Copyright 2023 Intergral GmbH
+ * Copyright (C) 2023 Intergral GmbH
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * 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.DEEPAPI;
+import com.intergral.deep.agent.api.resource.Resource;
+import java.util.Collections;
+
+/**
+ * This example expects the deep agent to be loaded via the javaagent vm option.
+ *
+ * See RunConfigurations for IDEA:
+ *
+ * - Agent Load without JavaAgent
+ * - Agent Load with JavaAgent
+ *
+ */
public class Main {
public static void main(String[] args) throws Throwable {
+
+ // Use the API to show the version of deep that is running
+ // If the Deep agent is not loaded then you will get an exception on this line
+ // java.lang.IllegalStateException: Must start Deep first!
+ System.out.println(DEEPAPI.api().getVersion());
+
+ // Use the API to register a plugin
+ // This plugin will attach the attribute 'example' to the created snapshot
+ // you should also see the log line 'custom plugin' when you run this example
+ DEEPAPI.api().registerPlugin((settings, snapshot) -> {
+ System.out.println("custom plugin");
+ return Resource.create(Collections.singletonMap("example", "agent_load"));
+ });
+
+ // 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());
+
final SimpleTest ts = new SimpleTest("This is a test", 2);
for (; ; ) {
try {
diff --git a/examples/agent-load/src/main/java/com/intergral/deep/examples/SimpleTest.java b/examples/agent-load/src/main/java/com/intergral/deep/examples/SimpleTest.java
index 46ffd04..3a42b27 100644
--- a/examples/agent-load/src/main/java/com/intergral/deep/examples/SimpleTest.java
+++ b/examples/agent-load/src/main/java/com/intergral/deep/examples/SimpleTest.java
@@ -1,17 +1,18 @@
/*
- * Copyright 2023 Intergral GmbH
+ * Copyright (C) 2023 Intergral GmbH
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * 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.
*
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
+ * 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;
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 f8a7f52..e4d5e85 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
@@ -18,12 +18,24 @@
package com.intergral.deep.examples;
+import com.intergral.deep.DEEPAPI;
import com.intergral.deep.Deep;
import com.intergral.deep.agent.api.IDeep;
import com.intergral.deep.agent.api.reflection.IReflection;
+import com.intergral.deep.agent.api.resource.Resource;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.Collections;
+/**
+ * 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 {
public static void main(String[] args) throws Throwable {
@@ -35,17 +47,34 @@ public static void main(String[] args) throws Throwable {
.getParent()
.getParent()
.resolve("agent/target/deep-1.0-SNAPSHOT.jar");
- System.setProperty("deep.jar.path", jarPath.toString());
+ // Dynamically configure and attach the deep agent
Deep.config()
+ .setJarPath(jarPath.toAbsolutePath().toString())
.setValue("service.url", "localhost:43315")
.setValue("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());
+
+ // Use the API to register a plugin
+ // This plugin will attach the attribute 'example' to the created snapshot
+ // you should also see the log line 'custom plugin' when you run this example
+ DEEPAPI.api().registerPlugin((settings, snapshot) -> {
+ System.out.println("custom plugin");
+ return Resource.create(Collections.singletonMap("example", "dynamic_load"));
+ });
+
+ // 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());
+
final SimpleTest ts = new SimpleTest("This is a test", 2);
for (; ; ) {
try {
diff --git a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java
index 2f0c21f..40d6bb4 100644
--- a/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java
+++ b/plugins/cf-plugin/src/main/java/com/intergral/deep/plugins/cf/CFPlugin.java
@@ -17,7 +17,7 @@
package com.intergral.deep.plugins.cf;
-import com.intergral.deep.agent.api.plugin.IEventContext;
+import com.intergral.deep.agent.api.plugin.ISnapshotContext;
import com.intergral.deep.agent.api.plugin.IPlugin;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.api.settings.ISettings;
@@ -26,7 +26,7 @@
public class CFPlugin implements IPlugin {
@Override
- public Resource decorate(final ISettings settings, final IEventContext context) {
+ public Resource decorate(final ISettings settings, final ISnapshotContext context) {
String appName = null;
try {
appName = context.evaluateExpression("APPLICATION.applicationname");
diff --git a/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java b/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java
index 5b65194..0d3e5e7 100644
--- a/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java
+++ b/plugins/java-plugin/src/main/java/com/intergral/deep/plugin/JavaPlugin.java
@@ -17,7 +17,7 @@
package com.intergral.deep.plugin;
-import com.intergral.deep.agent.api.plugin.IEventContext;
+import com.intergral.deep.agent.api.plugin.ISnapshotContext;
import com.intergral.deep.agent.api.plugin.IPlugin;
import com.intergral.deep.agent.api.resource.Resource;
import com.intergral.deep.agent.api.settings.ISettings;
@@ -33,7 +33,7 @@ public JavaPlugin() {
}
@Override
- public Resource decorate(final ISettings settings, final IEventContext snapshot) {
+ public Resource decorate(final ISettings settings, final ISnapshotContext snapshot) {
final Map javaMap = new HashMap<>(this.basic);
final Thread thread = Thread.currentThread();
diff --git a/test-utils/.gitignore b/test-utils/.gitignore
new file mode 100644
index 0000000..5ff6309
--- /dev/null
+++ b/test-utils/.gitignore
@@ -0,0 +1,38 @@
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### IntelliJ IDEA ###
+.idea/modules.xml
+.idea/jarRepositories.xml
+.idea/compiler.xml
+.idea/libraries/
+*.iws
+*.iml
+*.ipr
+
+### Eclipse ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
+
+### Mac OS ###
+.DS_Store
\ No newline at end of file