Split up manager responsibility by job
`TraceAnnotationsManager` is responsible for generating the rules for `@Trace`ed methods, `InstrumentationRulesManager` is responsible for our custom built Integrations.
MODIFY
dd-java-agent/dd-java-agent.gradle
dd-java-agent/dd-java-agent.gradle
@@ -10,15 +10,9 @@ apply from: "${rootDir}/gradle/jacoco.gradle"
jacocoTestReport.dependsOn ':dd-java-agent-ittests:test'
whitelistedInstructionClasses += whitelistedBranchClasses += [
- "com.datadoghq.trace.agent.integration.*",
- 'com.datadoghq.trace.agent.AnnotationsTracingAgent',
- 'com.datadoghq.trace.agent.AgentTracerConfig',
- 'com.datadoghq.trace.agent.TraceAnnotationsManager',
- 'com.datadoghq.trace.agent.InstrumentationChecker',
- 'com.datadoghq.trace.agent.DDJavaAgentInfo',
+ 'com.datadoghq.trace.agent.*',
+ 'com.datadoghq.trace.agent.integration.*',
'io.opentracing.contrib.mongo.TracingCommandListenerFactory',
- 'com.datadoghq.trace.agent.InstrumentationChecker.1',
- 'com.datadoghq.trace.agent.InstrumentationChecker.ArtifactSupport',
]
dependencies {
@@ -55,8 +49,8 @@ jar {
attributes(
"Main-Class": "com.datadoghq.trace.agent.DDJavaAgentInfo",
// I don't think we want to define this since we can't really load after startup:
- //"Agent-Class": "com.datadoghq.trace.agent.AnnotationsTracingAgent",
- "Premain-Class": "com.datadoghq.trace.agent.AnnotationsTracingAgent",
+ //"Agent-Class": "com.datadoghq.trace.agent.TracingAgent",
+ "Premain-Class": "com.datadoghq.trace.agent.TracingAgent",
"Can-Redefine-Classes": true,
"Can-Retransform-Classes": true,
// It is dangerous putting everything on the bootstrap classpath,

ADD
dd-java-agent/src/main/java/com/datadoghq/trace/agent/AgentRulesManager.java
+package com.datadoghq.trace.agent;
+
+import com.datadoghq.trace.DDTraceAnnotationsInfo;
+import com.datadoghq.trace.DDTraceInfo;
+import com.datadoghq.trace.resolver.DDTracerFactory;
+import com.datadoghq.trace.resolver.FactoryUtils;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import lombok.extern.slf4j.Slf4j;
+import org.jboss.byteman.agent.Retransformer;
+
+@Slf4j
+public class AgentRulesManager {
+
+ // Initialize the info classes so they print out their version info:
+ private static final String ddJavaAgentVersion = DDJavaAgentInfo.VERSION;
+ private static final String ddTraceVersion = DDTraceInfo.VERSION;
+ private static final String ddTraceAnnotationsVersion = DDTraceAnnotationsInfo.VERSION;
+
+ private static final String SPRING_BOOT_RULE = "spring-boot-rule.btm";
+
+ protected static volatile AgentRulesManager INSTANCE;
+
+ protected final Retransformer transformer;
+ protected final TracingAgentConfig agentTracerConfig;
+ protected final InstrumentationRulesManager instrumentationRulesManager;
+ protected final TraceAnnotationsManager traceAnnotationsManager;
+
+ public AgentRulesManager(Retransformer trans, TracingAgentConfig config) {
+ transformer = trans;
+ agentTracerConfig = config;
+ traceAnnotationsManager = new TraceAnnotationsManager(trans, config);
+ instrumentationRulesManager = new InstrumentationRulesManager(trans, config, this);
+ }
+
+ /**
+ * This method initializes the manager.
+ *
+ * @param trans The ByteMan retransformer
+ */
+ public static void initialize(final Retransformer trans) {
+ log.debug("Initializing {}", AgentRulesManager.class.getSimpleName());
+
+ TracingAgentConfig config =
+ FactoryUtils.loadConfigFromFilePropertyOrResource(
+ DDTracerFactory.SYSTEM_PROPERTY_CONFIG_PATH,
+ DDTracerFactory.CONFIG_PATH,
+ TracingAgentConfig.class);
+
+ log.debug("Configuration: {}", config.toString());
+
+ AgentRulesManager manager = new AgentRulesManager(trans, config);
+
+ INSTANCE = manager;
+
+ manager.loadRules(SPRING_BOOT_RULE, ClassLoader.getSystemClassLoader());
+ manager.traceAnnotationsManager.initialize();
+ }
+
+ /**
+ * This method loads any OpenTracing Agent rules (integration-rules.btm) found as resources within
+ * the supplied classloader.
+ *
+ * @param classLoader The classloader
+ */
+ protected List<String> loadRules(String rulesFileName, final ClassLoader classLoader) {
+ final List<String> scripts = new ArrayList<>();
+ if (transformer == null) {
+ log.warn(
+ "Attempt to load rules file {} on classloader {} before transformer initialized",
+ rulesFileName,
+ classLoader == null ? "bootstrap" : classLoader);
+ return scripts;
+ }
+
+ log.debug("Loading rules with classloader {}", classLoader == null ? "bootstrap" : classLoader);
+
+ final List<String> scriptNames = new ArrayList<>();
+
+ // Load default and custom rules
+ try {
+ final Enumeration<URL> iter = classLoader.getResources(rulesFileName);
+ while (iter.hasMoreElements()) {
+ loadRules(iter.nextElement().toURI(), scriptNames, scripts);
+ }
+
+ final StringWriter sw = new StringWriter();
+ try (PrintWriter writer = new PrintWriter(sw)) {
+ try {
+ transformer.installScript(scripts, scriptNames, writer);
+ } catch (final Exception e) {
+ log.warn("Failed to install scripts", e);
+ }
+ }
+ log.debug(sw.toString());
+ } catch (IOException | URISyntaxException e) {
+ log.warn("Failed to load rules", e);
+ }
+
+ log.debug("Rules loaded from {} on classloader {}", rulesFileName, classLoader);
+ if (log.isTraceEnabled()) {
+ for (final String rule : scripts) {
+ log.trace("Loading rule: {}", rule);
+ }
+ }
+ return scripts;
+ }
+
+ private static void loadRules(
+ final URI uri, final List<String> scriptNames, final List<String> scripts)
+ throws IOException {
+ log.debug("Load rules from URI uri={} ", uri);
+
+ final StringBuilder str = new StringBuilder();
+ try (InputStream is = uri.toURL().openStream()) {
+
+ final byte[] b = new byte[10240];
+ int len;
+ while ((len = is.read(b)) != -1) {
+ str.append(new String(b, 0, len));
+ }
+ }
+ scripts.add(str.toString());
+ scriptNames.add(uri.toString());
+ }
+}

ADD
dd-java-agent/src/main/java/com/datadoghq/trace/agent/InstrumentationRulesManager.java
+package com.datadoghq.trace.agent;
+
+import com.google.common.collect.Sets;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.extern.slf4j.Slf4j;
+import org.jboss.byteman.agent.Retransformer;
+
+/**
+ * This manager is loaded at pre-main. It loads all the scripts contained in all the 'oatrules.btm'
+ * resource files.
+ */
+@Slf4j
+public class InstrumentationRulesManager {
+
+ private static final String INTEGRATION_RULES = "integration-rules.btm";
+
+ private final Retransformer transformer;
+ private final TracingAgentConfig config;
+ private final AgentRulesManager agentRulesManager;
+
+ private final Set<ClassLoader> initializedClassloaders = Sets.newConcurrentHashSet();
+
+ public InstrumentationRulesManager(
+ Retransformer trans, TracingAgentConfig config, AgentRulesManager agentRulesManager) {
+ this.transformer = trans;
+ this.config = config;
+ this.agentRulesManager = agentRulesManager;
+
+ if (InstrumentationChecker.isClassPresent(
+ "org.springframework.boot.loader.LaunchedURLClassLoader",
+ ClassLoader.getSystemClassLoader())) {
+ log.info(
+ "Running in the context of a Spring Boot executable jar. Deferring rule loading to run in the LaunchedURLClassLoader.");
+ agentRulesManager.loadRules("spring-boot-rule.btm", ClassLoader.getSystemClassLoader());
+ } else {
+ initialize(ClassLoader.getSystemClassLoader());
+ }
+ }
+
+ public static void registerClassLoad(Object obj) {
+ log.info("Calling initialize with {}", obj);
+ ClassLoader cl;
+ if (obj instanceof ClassLoader) {
+ cl = (ClassLoader) obj;
+ } else {
+ cl = obj.getClass().getClassLoader();
+ }
+
+ AgentRulesManager.INSTANCE.instrumentationRulesManager.initialize(cl);
+ }
+
+ /**
+ * This method is separated out from initialize to allow Spring Boot's LaunchedURLClassLoader to
+ * call it once it is loaded.
+ *
+ * @param classLoader
+ */
+ public void initialize(ClassLoader classLoader) {
+ synchronized (classLoader) {
+ if (initializedClassloaders.contains(classLoader)) {
+ return;
+ }
+ initializedClassloaders.add(classLoader);
+ }
+
+ final List<String> loadedScripts = agentRulesManager.loadRules(INTEGRATION_RULES, classLoader);
+
+ //Check if some rules have to be uninstalled
+ final List<String> uninstallScripts = InstrumentationChecker.getUnsupportedRules(classLoader);
+ if (config != null) {
+ final List<String> disabledInstrumentations = config.getDisabledInstrumentations();
+ if (disabledInstrumentations != null && !disabledInstrumentations.isEmpty()) {
+ uninstallScripts.addAll(disabledInstrumentations);
+ }
+ }
+
+ try {
+ uninstallScripts(loadedScripts, uninstallScripts);
+ } catch (Exception e) {
+ log.warn("Error uninstalling scripts", e);
+ }
+ }
+
+ /**
+ * Uninstall some scripts from a list of patterns. All the rules that contain the pattern will be
+ * uninstalled
+ *
+ * @param patterns not case sensitive (eg. "mongo", "apache http", "elasticsearch", etc...])
+ */
+ private void uninstallScripts(final List<String> installedScripts, final List<String> patterns)
+ throws Exception {
+ final Set<String> rulesToRemove = new HashSet<>();
+
+ for (final String strPattern : patterns) {
+ final Pattern pattern = Pattern.compile("(?i)RULE [^\n]*" + strPattern + "[^\n]*\n");
+ for (final String loadedScript : installedScripts) {
+ final Matcher matcher = pattern.matcher(loadedScript);
+ while (matcher.find()) {
+ rulesToRemove.add(matcher.group());
+ }
+ }
+ }
+
+ if (!rulesToRemove.isEmpty()) {
+ final StringWriter sw = new StringWriter();
+ try (PrintWriter pr = new PrintWriter(sw)) {
+ transformer.removeScripts(new ArrayList<>(rulesToRemove), pr);
+ }
+ log.info("Uninstall rule scripts: {}", rulesToRemove.toString());
+ }
+ }
+}

MODIFY
dd-java-agent/src/main/java/com/datadoghq/trace/agent/TraceAnnotationsManager.java
dd-java-agent/src/main/java/com/datadoghq/trace/agent/TraceAnnotationsManager.java
package com.datadoghq.trace.agent;
-import com.datadoghq.trace.DDTraceAnnotationsInfo;
-import com.datadoghq.trace.DDTraceInfo;
import com.datadoghq.trace.Trace;
-import com.datadoghq.trace.resolver.DDTracerFactory;
-import com.datadoghq.trace.resolver.FactoryUtils;
-import java.io.IOException;
-import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
@@ -38,18 +23,10 @@ import org.reflections.util.FilterBuilder;
/**
* This manager is loaded at pre-main.
*
- * <p>It loads all the scripts contained in all the 'oatrules.btm' resource files then instrument
- * all the methods annoted with the @Trace.
+ * <p>This class adds rules to instrument all the methods annotated with the @Trace.
*/
@Slf4j
public class TraceAnnotationsManager {
-
- // Initialize the info classes so they print out their version info:
- private static final String ddJavaAgentVersion = DDJavaAgentInfo.VERSION;
- private static final String ddTraceVersion = DDTraceInfo.VERSION;
- private static final String ddTraceAnnotationsVersion = DDTraceAnnotationsInfo.VERSION;
-
- private static final String AGENT_RULES = "otarules.btm";
private static final String CURRENT_SPAN_EXISTS = "IF TRUE\n";
private static final String BUILD_SPAN = "DO\n" + "getTracer().buildSpan(\"";
private static final String CLOSE_PARENTHESIS = "\")";
@@ -62,61 +39,18 @@ public class TraceAnnotationsManager {
+ "DO\n"
+ "span.setTag(io.opentracing.tag.Tags.ERROR.getKey(),\"true\");\n"
+ "span.deactivate();\n";
- private static Retransformer transformer;
- private static AgentTracerConfig agentTracerConfig;
-
- /**
- * This method initializes the manager.
- *
- * @param trans The ByteMan retransformer
- */
- public static void initialize(final Retransformer trans) {
- log.debug("Initializing {}", TraceAnnotationsManager.class.getSimpleName());
- transformer = trans;
- //Load configuration
- agentTracerConfig =
- FactoryUtils.loadConfigFromFilePropertyOrResource(
- DDTracerFactory.SYSTEM_PROPERTY_CONFIG_PATH,
- DDTracerFactory.CONFIG_PATH,
- AgentTracerConfig.class);
- log.debug("Configuration: {}", agentTracerConfig.toString());
-
- if (InstrumentationChecker.isClassPresent(
- "org.springframework.boot.loader.LaunchedURLClassLoader",
- ClassLoader.getSystemClassLoader())) {
- log.info(
- "Running in the context of a Spring Boot executable jar. Deferring rule loading to run in the LaunchedURLClassLoader.");
- loadRules("spring-boot-rule.btm", ClassLoader.getSystemClassLoader());
- } else {
- finishInitialization(ClassLoader.getSystemClassLoader());
- }
- }
-
- /**
- * This method is separated out from initialize to allow Spring Boot's LaunchedURLClassLoader to
- * call it once it is loaded.
- *
- * @param classLoader
- */
- public static void finishInitialization(ClassLoader classLoader) {
- final List<String> loadedScripts = loadRules(AGENT_RULES, classLoader);
+ private final Retransformer transformer;
+ private final TracingAgentConfig agentTracerConfig;
- //Check if some rules have to be uninstalled
- final List<String> uninstallScripts =
- InstrumentationChecker.getUnsupportedRules(ClassLoader.getSystemClassLoader());
- if (agentTracerConfig != null) {
- final List<String> disabledInstrumentations = agentTracerConfig.getDisabledInstrumentations();
- if (disabledInstrumentations != null && !disabledInstrumentations.isEmpty()) {
- uninstallScripts.addAll(disabledInstrumentations);
- }
+ public TraceAnnotationsManager(Retransformer trans, TracingAgentConfig config) {
+ transformer = trans;
+ agentTracerConfig = config;
}
- try {
- uninstallScripts(loadedScripts, uninstallScripts);
- } catch (Exception e) {
- log.warn("Error uninstalling scripts", e);
- }
+ /** This method initializes the manager. */
+ public void initialize() {
+ log.debug("Initializing {}", TraceAnnotationsManager.class.getSimpleName());
//Check if annotations are enabled
if (agentTracerConfig != null
@@ -126,87 +60,8 @@ public class TraceAnnotationsManager {
}
}
- /**
- * Uninstall some scripts from a list of patterns. All the rules that contain the pattern will be
- * uninstalled
- *
- * @param patterns not case sensitive (eg. "mongo", "apache http", "elasticsearch", etc...])
- */
- public static void uninstallScripts(
- final List<String> installedScripts, final List<String> patterns) throws Exception {
- final Set<String> rulesToRemove = new HashSet<>();
-
- for (final String strPattern : patterns) {
- final Pattern pattern = Pattern.compile("(?i)RULE [^\n]*" + strPattern + "[^\n]*\n");
- for (final String loadedScript : installedScripts) {
- final Matcher matcher = pattern.matcher(loadedScript);
- while (matcher.find()) {
- rulesToRemove.add(matcher.group());
- }
- }
- }
-
- if (!rulesToRemove.isEmpty()) {
- final StringWriter sw = new StringWriter();
- try (PrintWriter pr = new PrintWriter(sw)) {
- transformer.removeScripts(new ArrayList<>(rulesToRemove), pr);
- }
- log.info("Uninstall rule scripts: {}", rulesToRemove.toString());
- }
- }
-
- /**
- * This method loads any OpenTracing Agent rules (otarules.btm) found as resources within the
- * supplied classloader.
- *
- * @param classLoader The classloader
- */
- public static List<String> loadRules(String rulesFileName, final ClassLoader classLoader) {
- final List<String> scripts = new ArrayList<>();
- if (transformer == null) {
- log.warn(
- "Attempt to load rules file {} on classloader {} before transformer initialized",
- rulesFileName,
- classLoader == null ? "bootstrap" : classLoader);
- return scripts;
- }
-
- log.debug("Loading rules with classloader {}", classLoader == null ? "bootstrap" : classLoader);
-
- final List<String> scriptNames = new ArrayList<>();
-
- // Load default and custom rules
- try {
- final Enumeration<URL> iter = classLoader.getResources(rulesFileName);
- while (iter.hasMoreElements()) {
- loadRules(iter.nextElement().toURI(), scriptNames, scripts);
- }
-
- final StringWriter sw = new StringWriter();
- try (PrintWriter writer = new PrintWriter(sw)) {
- try {
- transformer.installScript(scripts, scriptNames, writer);
- } catch (final Exception e) {
- log.warn("Failed to install scripts", e);
- }
- }
- log.debug(sw.toString());
- } catch (IOException | URISyntaxException e) {
- log.warn("Failed to load rules", e);
- }
-
- log.debug("Rules loaded from {} on classloader {}", rulesFileName, classLoader);
- if (log.isTraceEnabled()) {
- for (final String rule : scripts) {
- log.trace("Loading rule: {}", rule);
- }
- }
- return scripts;
- }
-
/** Find all the methods annoted with @Trace and inject rules */
- public static void loadAnnotationsRules(final String... scannedPackages) {
-
+ private void loadAnnotationsRules(final String... scannedPackages) {
log.info(
"Looking for annotations over the following packages: {}", Arrays.asList(scannedPackages));
final long cur = System.currentTimeMillis();
@@ -282,7 +137,7 @@ public class TraceAnnotationsManager {
+ " ms. You can accelerate this process by refining the packages you want to scan with `scannedPackages` in the dd-trace.yaml configuration file.");
}
- private static RuleScript createRuleScript(
+ private RuleScript createRuleScript(
final String ruleNamePrefix,
final CtClass cc,
final CtMethod javassistMethod,
@@ -308,25 +163,7 @@ public class TraceAnnotationsManager {
return ruleScript;
}
- private static void loadRules(
- final URI uri, final List<String> scriptNames, final List<String> scripts)
- throws IOException {
- log.debug("Load rules from URI uri={} ", uri);
-
- final StringBuilder str = new StringBuilder();
- try (InputStream is = uri.toURL().openStream()) {
-
- final byte[] b = new byte[10240];
- int len;
- while ((len = is.read(b)) != -1) {
- str.append(new String(b, 0, len));
- }
- }
- scripts.add(str.toString());
- scriptNames.add(uri.toString());
- }
-
- private static String buildSpan(final CtMethod javassistMethod) {
+ private String buildSpan(final CtMethod javassistMethod) {
try {
final Trace trace = (Trace) javassistMethod.getAnnotation(Trace.class);
if (trace.operationName() != null & !trace.operationName().isEmpty()) {

RENAME
dd-java-agent/src/main/java/com/datadoghq/trace/agent/AnnotationsTracingAgent.java
dd-java-agent/src/main/java/com/datadoghq/trace/agent/TracingAgent.java
@@ -25,17 +25,17 @@ import lombok.extern.slf4j.Slf4j;
* and the manager class.
*/
@Slf4j
-public class AnnotationsTracingAgent extends OpenTracingAgent {
+public class TracingAgent extends OpenTracingAgent {
public static void premain(String agentArgs, final Instrumentation inst) throws Exception {
agentArgs = addManager(agentArgs);
- log.debug("Using premain for loading {}", AnnotationsTracingAgent.class.getSimpleName());
+ log.debug("Using premain for loading {}", TracingAgent.class.getSimpleName());
org.jboss.byteman.agent.Main.premain(agentArgs, inst);
}
public static void agentmain(String agentArgs, final Instrumentation inst) throws Exception {
agentArgs = addManager(agentArgs);
- log.debug("Using agentmain for loading {}", AnnotationsTracingAgent.class.getSimpleName());
+ log.debug("Using agentmain for loading {}", TracingAgent.class.getSimpleName());
org.jboss.byteman.agent.Main.agentmain(agentArgs, inst);
}
@@ -45,7 +45,7 @@ public class AnnotationsTracingAgent extends OpenTracingAgent {
} else {
agentArgs += ",";
}
- agentArgs += "manager:" + TraceAnnotationsManager.class.getName();
+ agentArgs += "manager:" + AgentRulesManager.class.getName();
log.debug("Agent args=: {}", agentArgs);
return agentArgs;
}

RENAME
dd-java-agent/src/main/java/com/datadoghq/trace/agent/AgentTracerConfig.java
dd-java-agent/src/main/java/com/datadoghq/trace/agent/TracingAgentConfig.java
@@ -5,7 +5,7 @@ import java.util.ArrayList;
import java.util.List;
/** Configuration POJO for the agent */
-public class AgentTracerConfig extends TracerConfig {
+public class TracingAgentConfig extends TracerConfig {
private List<String> disabledInstrumentations = new ArrayList<String>();

MODIFY
dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml
dd-java-agent/src/main/resources/dd-trace-supported-framework.yaml
### This file define all supported libraries
### Syntax:
-### <rulename>: # the rule name defined in the otarules.btm
+### <rulename>: # the rule name defined in the integration-rules.btm
### - artifact: <artifactId> # the artifact name to be tested
### supported_version: # a regex expression to express the version required by the rule
### identifying_present_classes: # a list of classes that distinctly identify the range of supported libraries

RENAME
dd-java-agent/src/main/resources/otarules.btm
dd-java-agent/src/main/resources/integration-rules.btm
MODIFY
dd-java-agent/src/main/resources/spring-boot-rule.btm
dd-java-agent/src/main/resources/spring-boot-rule.btm
@@ -9,5 +9,5 @@ METHOD <init>
AT EXIT
IF TRUE
DO
- com.datadoghq.trace.agent.TraceAnnotationsManager.finishInitialization($0);
+ com.datadoghq.trace.agent.InstrumentationRulesManager.registerClassLoad($0);
ENDRULE

Split up manager responsibility by job

`TraceAnnotationsManager` is responsible for generating the rules for `@Trace`ed methods, `InstrumentationRulesManager` is responsible for our custom built Integrations.