From 1c0c8e2aa48725df27a3d6cf615956a6ce56af81 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Tue, 27 Jun 2023 11:03:13 +0100 Subject: [PATCH 1/2] feat(start): change start up to await deep start feat(api): add tracepoint create apis feat(api): add plugin registration api feat(docs): improve java docs on plugin api types feat(examples): improve examples to show api features --- .../com/intergral/deep/agent/api/IDeep.java | 45 ++++++++++++++++ .../IEventContext.java => IRegistration.java} | 12 +++-- .../agent/api/plugin/EvaluationException.java | 49 +++++++++++++++++ .../deep/agent/api/plugin/IPlugin.java | 44 ++++++++++++++- .../agent/api/plugin/ISnapshotContext.java | 33 ++++++++++++ .../agent/api/tracepoint/ITracepoint.java | 47 ++++++++++++++++ .../com/intergral/deep/agent/AgentImpl.java | 27 ++++++---- .../com/intergral/deep/agent/DeepAgent.java | 32 ++++++++++- .../deep/agent/push/PushService.java | 8 +-- .../deep/agent/settings/Settings.java | 19 ++++++- .../agent/tracepoint/ITracepointConfig.java | 3 +- .../tracepoint/TracepointConfigService.java | 30 +++++++++-- .../tracepoint/handler/FrameProcessor.java | 15 ++++-- .../main/java/com/intergral/deep/DEEPAPI.java | 46 ++++++++++++++++ .../main/java/com/intergral/deep/Deep.java | 36 ++++++++++++- .../java/com/intergral/deep/DeepLoader.java | 21 +++----- examples/agent-load/pom.xml | 7 +++ .../com/intergral/deep/examples/BaseTest.java | 21 ++++---- .../com/intergral/deep/examples/Main.java | 53 +++++++++++++++---- .../intergral/deep/examples/SimpleTest.java | 21 ++++---- .../com/intergral/deep/examples/Main.java | 31 ++++++++++- .../intergral/deep/plugins/cf/CFPlugin.java | 4 +- .../com/intergral/deep/plugin/JavaPlugin.java | 4 +- test-utils/.gitignore | 38 +++++++++++++ 24 files changed, 564 insertions(+), 82 deletions(-) rename agent-api/src/main/java/com/intergral/deep/agent/api/{plugin/IEventContext.java => IRegistration.java} (78%) create mode 100644 agent-api/src/main/java/com/intergral/deep/agent/api/plugin/EvaluationException.java create mode 100644 agent-api/src/main/java/com/intergral/deep/agent/api/plugin/ISnapshotContext.java create mode 100644 agent-api/src/main/java/com/intergral/deep/agent/api/tracepoint/ITracepoint.java create mode 100644 deep/src/main/java/com/intergral/deep/DEEPAPI.java create mode 100644 test-utils/.gitignore 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..ea3d464 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 () -> DeepAgent.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 () -> DeepAgent.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: + *

+ */ 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: + *

+ */ 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 From 717af3e8c709bf917431c6977442b5f7e0bd23b8 Mon Sep 17 00:00:00 2001 From: Ben Donnelly Date: Tue, 27 Jun 2023 11:09:05 +0100 Subject: [PATCH 2/2] fix(pmd): correct pmd failure --- agent/src/main/java/com/intergral/deep/agent/DeepAgent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ea3d464..273e0a9 100644 --- a/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java +++ b/agent/src/main/java/com/intergral/deep/agent/DeepAgent.java @@ -74,7 +74,7 @@ public String getVersion() { public IPluginRegistration registerPlugin(final IPlugin plugin) { this.settings.addPlugin(plugin); - return () -> DeepAgent.this.settings.removePlugin(plugin); + return () -> this.settings.removePlugin(plugin); } @Override @@ -86,6 +86,6 @@ public ITracepointRegistration registerTracepoint(final String path, final int l 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 () -> DeepAgent.this.tracepointConfig.removeCustom(tracePointConfig); + return () -> this.tracepointConfig.removeCustom(tracePointConfig); } }