Merge pull request #505 from DataDog/mar-kolya/jmxfetch

Use JMXFetch as a library
This commit is contained in:
Nikolay Martynov 2018-10-18 16:22:41 -04:00 committed by GitHub
commit c0e4c7bad2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 457 additions and 64 deletions

View File

@ -0,0 +1,52 @@
package datadog.trace.agent
import datadog.trace.agent.test.IntegrationTestUtils
import datadog.trace.api.Config
import org.junit.Rule
import org.junit.contrib.java.lang.system.RestoreSystemProperties
import spock.lang.Specification
import java.lang.reflect.Method
class JMXFetchTest extends Specification {
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties()
def "test jmxfetch"() {
setup:
def currentContextLoader = Thread.currentThread().getContextClassLoader()
DatagramSocket socket = new DatagramSocket(0)
System.properties.setProperty("dd.jmxfetch.enabled", "true")
System.properties.setProperty("dd.jmxfetch.statsd.port", Integer.toString(socket.localPort))
// Overwrite writer type to disable console jmxfetch reporter
System.properties.setProperty("dd.writer.type", "DDAgentWriter")
def classLoader = IntegrationTestUtils.getJmxFetchClassLoader()
// Have to set this so JMXFetch knows where to find resources
Thread.currentThread().setContextClassLoader(classLoader)
final Class<?> jmxFetchAgentClass =
classLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch")
final Method jmxFetchInstallerMethod = jmxFetchAgentClass.getDeclaredMethod("run", Config)
jmxFetchInstallerMethod.setAccessible(true)
jmxFetchInstallerMethod.invoke(null, new Config())
byte[] buf = new byte[1500]
DatagramPacket packet = new DatagramPacket(buf, buf.length)
socket.receive(packet)
String received = new String(packet.getData(), 0, packet.getLength())
Set<String> threads = Thread.getAllStackTraces().keySet().collect { it.name }
expect:
threads.contains("dd-jmx-collector")
received.contains("jvm.")
cleanup:
jmxFetchInstallerMethod.setAccessible(false)
socket.close()
Thread.currentThread().setContextClassLoader(currentContextLoader)
}
}

View File

@ -56,13 +56,13 @@ class ShadowPackageRenamingTest extends Specification {
def "agent jar contains no bootstrap classes"() {
setup:
final ClassPath agentClasspath = ClassPath.from(IntegrationTestUtils.getAgentClassLoader())
final ClassPath jmxFetchClasspath = ClassPath.from(IntegrationTestUtils.getJmxFetchClassLoader())
final ClassPath bootstrapClasspath = ClassPath.from(IntegrationTestUtils.getBootstrapProxy())
final Set<String> bootstrapClasses = new HashSet<>()
final String[] bootstrapPrefixes = IntegrationTestUtils.getBootstrapPackagePrefixes()
final String[] agentPrefixes = IntegrationTestUtils.getAgentPackagePrefixes()
final List<String> badBootstrapPrefixes = []
final List<String> badAgentPrefixes = []
for (ClassPath.ClassInfo info : bootstrapClasspath.getAllClasses()) {
bootstrapClasses.add(info.getName())
// make sure all bootstrap classes can be loaded from system
@ -79,10 +79,11 @@ class ShadowPackageRenamingTest extends Specification {
}
}
final List<ClassPath.ClassInfo> duplicateClassFile = new ArrayList<>()
final List<ClassPath.ClassInfo> agentDuplicateClassFile = new ArrayList<>()
final List<String> badAgentPrefixes = []
for (ClassPath.ClassInfo classInfo : agentClasspath.getAllClasses()) {
if (bootstrapClasses.contains(classInfo.getName())) {
duplicateClassFile.add(classInfo)
agentDuplicateClassFile.add(classInfo)
}
boolean goodPrefix = false
for (int i = 0; i < agentPrefixes.length; ++i) {
@ -96,9 +97,28 @@ class ShadowPackageRenamingTest extends Specification {
}
}
final List<ClassPath.ClassInfo> jmxFetchDuplicateClassFile = new ArrayList<>()
final List<String> badJmxFetchPrefixes = []
for (ClassPath.ClassInfo classInfo : jmxFetchClasspath.getAllClasses()) {
if (bootstrapClasses.contains(classInfo.getName())) {
jmxFetchDuplicateClassFile.add(classInfo)
}
boolean goodPrefix = true
for (int i = 0; i < bootstrapPrefixes.length; ++i) {
if (classInfo.getName().startsWith(bootstrapPrefixes[i])) {
goodPrefix = false
break
}
}
if (!goodPrefix) {
badJmxFetchPrefixes.add(classInfo.getName())
}
}
expect:
duplicateClassFile == []
agentDuplicateClassFile == []
badBootstrapPrefixes == []
badAgentPrefixes == []
badJmxFetchPrefixes == []
}
}

View File

@ -32,12 +32,21 @@ public class IntegrationTestUtils {
/** Returns the classloader the core agent is running on. */
public static ClassLoader getAgentClassLoader() {
return getTracingAgentFieldClassloader("AGENT_CLASSLOADER");
}
/** Returns the classloader the jmxfetch is running on. */
public static ClassLoader getJmxFetchClassLoader() {
return getTracingAgentFieldClassloader("JMXFETCH_CLASSLOADER");
}
private static ClassLoader getTracingAgentFieldClassloader(final String fieldName) {
Field classloaderField = null;
try {
Class<?> tracingAgentClass =
tracingAgentClass =
ClassLoader.getSystemClassLoader().loadClass("datadog.trace.agent.TracingAgent");
classloaderField = tracingAgentClass.getDeclaredField("AGENT_CLASSLOADER");
classloaderField = tracingAgentClass.getDeclaredField(fieldName);
classloaderField.setAccessible(true);
return (ClassLoader) classloaderField.get(null);
} catch (final Exception e) {

View File

@ -0,0 +1,30 @@
plugins {
id "com.github.johnrengelman.shadow"
}
apply from: "${rootDir}/gradle/java.gradle"
dependencies {
compile 'com.heliosapm.jmxlocal:jmxlocal:1.0'
compile 'com.datadoghq:jmxfetch:0.21.0'
compile deps.slf4j
compile project(':dd-trace-api')
}
configurations {
// exclude bootstrap dependencies from shadowJar
runtime.exclude module: deps.opentracing
runtime.exclude module: deps.slf4j
runtime.exclude group: 'org.slf4j'
runtime.exclude group: 'io.opentracing'
}
shadowJar {
dependencies {
exclude(project(':dd-java-agent:agent-bootstrap'))
exclude(project(':dd-trace-api'))
}
}
jar {
classifier = 'unbundled'
}

View File

@ -0,0 +1,101 @@
package datadog.trace.agent.jmxfetch;
import com.google.common.collect.ImmutableList;
import datadog.trace.api.Config;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.datadog.jmxfetch.App;
import org.datadog.jmxfetch.AppConfig;
@Slf4j
public class JMXFetch {
public static final ImmutableList<String> DEFAULT_CONFIGS =
ImmutableList.of("jmxfetch-config.yaml");
private static final int SLEEP_AFTER_JMXFETCH_EXITS = 5000;
public static final void run() {
run(Config.get());
}
// This is used by tests
private static void run(final Config config) {
if (!config.isJmxFetchEnabled()) {
log.info("JMXFetch is disabled");
return;
}
final List<String> metricsConfigs = config.getJmxFetchMetricsConfigs();
final Integer checkPeriod = config.getJmxFetchCheckPeriod();
final Integer refreshBeansPeriod = config.getJmxFetchRefreshBeansPeriod();
final String reporter = getReporter(config);
final String logLocation = getLogLocation();
final String logLevel = getLogLevel();
log.error(
"JMXFetch config: {} {} {} {} {} {}",
metricsConfigs,
checkPeriod,
refreshBeansPeriod,
reporter,
logLocation,
logLevel);
final AppConfig appConfig =
AppConfig.create(
DEFAULT_CONFIGS,
metricsConfigs,
checkPeriod,
refreshBeansPeriod,
reporter,
logLocation,
logLevel);
final Thread thread =
new Thread(
new Runnable() {
@Override
public void run() {
while (true) {
try {
final int result = App.run(appConfig);
log.error("jmx collector exited with result: " + result);
} catch (final Exception e) {
log.error("Exception in jmx collector thread", e);
}
try {
Thread.sleep(SLEEP_AFTER_JMXFETCH_EXITS);
} catch (final InterruptedException e) {
// It looks like JMXFetch itself eats up InterruptedException, so we will do
// same here for consistency
log.error("JMXFetch was interupted, ignoring", e);
}
}
}
});
thread.setName("dd-jmx-collector");
thread.setDaemon(true);
thread.start();
}
private static String getReporter(final Config config) {
if (Config.LOGGING_WRITER_TYPE.equals(config.getWriterType())) {
// If logging writer is enabled then also enable console reporter in JMXFetch
return "console";
}
final String host =
config.getJmxFetchStatsdHost() == null
? config.getAgentHost()
: config.getJmxFetchStatsdHost();
return "statsd:" + host + ":" + config.getJmxFetchStatsdPort();
}
private static String getLogLocation() {
return System.getProperty("org.slf4j.simpleLogger.logFile", "System.err");
}
private static String getLogLevel() {
return System.getProperty("org.slf4j.simpleLogger.defaultLogLevel", "info").toUpperCase();
}
}

View File

@ -0,0 +1,7 @@
init_config:
is_jmx: true
instances:
- jmx_url: service:jmx:local:///
conf:
# Intentionally left empty for now

View File

@ -52,7 +52,7 @@ def includeShadowJar(subproject, jarname) {
includeShadowJar(project(':dd-java-agent:agent-bootstrap'), 'agent-bootstrap.jar.zip')
includeShadowJar(project(':dd-java-agent:instrumentation'), 'agent-tooling-and-instrumentation.jar.zip')
includeShadowJar(project(':dd-java-agent:agent-jmxfetch'), 'agent-jmxfetch.jar.zip')
jar {
classifier = 'unbundled'

View File

@ -31,7 +31,9 @@ import java.util.jar.JarFile;
/** Entry point for initializing the agent. */
public class TracingAgent {
private static boolean inited = false;
private static ClassLoader AGENT_CLASSLOADER = null;
private static ClassLoader JMXFETCH_CLASSLOADER = null;
public static void premain(final String agentArgs, final Instrumentation inst) throws Exception {
startAgent(agentArgs, inst);
@ -44,12 +46,15 @@ public class TracingAgent {
private static synchronized void startAgent(final String agentArgs, final Instrumentation inst)
throws Exception {
if (null == AGENT_CLASSLOADER) {
if (!inited) {
final File toolingJar =
extractToTmpFile(
TracingAgent.class.getClassLoader(),
"agent-tooling-and-instrumentation.jar.zip",
"agent-tooling-and-instrumentation.jar");
final File jmxFetchJar =
extractToTmpFile(
TracingAgent.class.getClassLoader(), "agent-jmxfetch.jar.zip", "agent-jmxfetch.jar");
final File bootstrapJar =
extractToTmpFile(
TracingAgent.class.getClassLoader(),
@ -59,6 +64,7 @@ public class TracingAgent {
// bootstrap jar must be appended before agent classloader is created.
inst.appendToBootstrapClassLoaderSearch(new JarFile(bootstrapJar));
final ClassLoader agentClassLoader = createDatadogClassLoader(bootstrapJar, toolingJar);
final ClassLoader jmxFetchClassLoader = createDatadogClassLoader(bootstrapJar, jmxFetchJar);
final ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
try {
@ -81,10 +87,22 @@ public class TracingAgent {
logVersionInfoMethod.invoke(null);
}
Thread.currentThread().setContextClassLoader(jmxFetchClassLoader);
{ // install jmxfetch tracer
// We would like jmxfetch to be loaded after APM agent to avoid needing to retransform
// classes
final Class<?> jmxFetchAgentClass =
jmxFetchClassLoader.loadClass("datadog.trace.agent.jmxfetch.JMXFetch");
final Method jmxFetchInstallerMethod = jmxFetchAgentClass.getMethod("run");
jmxFetchInstallerMethod.invoke(null);
}
AGENT_CLASSLOADER = agentClassLoader;
JMXFETCH_CLASSLOADER = jmxFetchClassLoader;
} finally {
Thread.currentThread().setContextClassLoader(contextLoader);
}
inited = true;
}
}

View File

@ -63,13 +63,13 @@ public class SpockRunner extends Sputnik {
private final InstrumentationClassLoader customLoader;
public SpockRunner(Class<?> clazz)
public SpockRunner(final Class<?> clazz)
throws InitializationError, NoSuchFieldException, SecurityException, IllegalArgumentException,
IllegalAccessException {
super(shadowTestClass(clazz));
assertNoBootstrapClassesInTestClass(clazz);
// access the classloader created in shadowTestClass above
Field clazzField = Sputnik.class.getDeclaredField("clazz");
final Field clazzField = Sputnik.class.getDeclaredField("clazz");
try {
clazzField.setAccessible(true);
customLoader =
@ -79,7 +79,7 @@ public class SpockRunner extends Sputnik {
}
}
private static void assertNoBootstrapClassesInTestClass(Class<?> testClass) {
private static void assertNoBootstrapClassesInTestClass(final Class<?> testClass) {
for (final Field field : testClass.getDeclaredFields()) {
assertNotBootstrapClass(testClass, field.getType());
}
@ -92,7 +92,7 @@ public class SpockRunner extends Sputnik {
}
}
private static void assertNotBootstrapClass(Class<?> testClass, Class<?> clazz) {
private static void assertNotBootstrapClass(final Class<?> testClass, final Class<?> clazz) {
if ((!clazz.isPrimitive()) && isBootstrapClass(clazz.getName())) {
throw new IllegalStateException(
testClass.getName()
@ -101,7 +101,7 @@ public class SpockRunner extends Sputnik {
}
}
private static boolean isBootstrapClass(String className) {
private static boolean isBootstrapClass(final String className) {
for (int i = 0; i < TEST_BOOTSTRAP_PREFIXES.length; ++i) {
if (className.startsWith(TEST_BOOTSTRAP_PREFIXES[i])) {
return true;
@ -113,11 +113,11 @@ public class SpockRunner extends Sputnik {
// Shadow the test class with bytes loaded by InstrumentationClassLoader
private static Class<?> shadowTestClass(final Class<?> clazz) {
try {
InstrumentationClassLoader customLoader =
final InstrumentationClassLoader customLoader =
new InstrumentationClassLoader(
datadog.trace.agent.test.SpockRunner.class.getClassLoader(), clazz.getName());
return customLoader.shadow(clazz);
} catch (Exception e) {
} catch (final Exception e) {
throw new IllegalStateException(e);
}
}
@ -141,14 +141,14 @@ public class SpockRunner extends Sputnik {
// Utils cannot be referenced before this line, as its static initializers load bootstrap
// classes (for example, the bootstrap proxy).
datadog.trace.agent.tooling.Utils.getBootstrapProxy().addURL(bootstrapJar.toURI().toURL());
} catch (IOException e) {
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
private static File createBootstrapJar() throws IOException {
Set<String> bootstrapClasses = new HashSet<String>();
for (ClassPath.ClassInfo info : TestUtils.getTestClasspath().getAllClasses()) {
final Set<String> bootstrapClasses = new HashSet<>();
for (final ClassPath.ClassInfo info : TestUtils.getTestClasspath().getAllClasses()) {
// if info starts with bootstrap prefix: add to bootstrap jar
if (isBootstrapClass(info.getName())) {
bootstrapClasses.add(info.getResourceName());
@ -165,48 +165,39 @@ public class SpockRunner extends Sputnik {
final ClassLoader parent;
final String shadowPrefix;
public InstrumentationClassLoader(ClassLoader parent, String shadowPrefix) {
public InstrumentationClassLoader(final ClassLoader parent, final String shadowPrefix) {
super(parent);
this.parent = parent;
this.shadowPrefix = shadowPrefix;
}
/** Forcefully inject the bytes of clazz into this classloader. */
public Class<?> shadow(Class<?> clazz) throws IOException {
Class<?> loaded = this.findLoadedClass(clazz.getName());
public Class<?> shadow(final Class<?> clazz) throws IOException {
final Class<?> loaded = findLoadedClass(clazz.getName());
if (loaded != null && loaded.getClassLoader() == this) {
return loaded;
}
final ClassFileLocator locator = ClassFileLocator.ForClassLoader.of(clazz.getClassLoader());
final byte[] classBytes = locator.locate(clazz.getName()).resolve();
return this.defineClass(clazz.getName(), classBytes, 0, classBytes.length);
return defineClass(clazz.getName(), classBytes, 0, classBytes.length);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
protected Class<?> loadClass(final String name, final boolean resolve)
throws ClassNotFoundException {
synchronized (super.getClassLoadingLock(name)) {
Class c = this.findLoadedClass(name);
final Class c = findLoadedClass(name);
if (c != null) {
return c;
}
if (name.startsWith(shadowPrefix)) {
try {
return shadow(super.loadClass(name, resolve));
} catch (Exception e) {
} catch (final Exception e) {
}
}
/*
if (!name.startsWith("datadog.trace.agent.test.")) {
for (int i = 0; i < AGENT_PACKAGE_PREFIXES.length; ++i) {
if (name.startsWith(AGENT_PACKAGE_PREFIXES[i])) {
throw new ClassNotFoundException(
"refusing to load agent class: " + name + " on test classloader.");
}
}
}
*/
return parent.loadClass(name);
}
}

View File

@ -1,7 +1,9 @@
package datadog.trace.api;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import lombok.Getter;
@ -26,14 +28,20 @@ public class Config {
private static final Config INSTANCE = new Config();
public static final String SERVICE_NAME = "service.name";
public static final String SERVICE_MAPPING = "service.mapping";
public static final String WRITER_TYPE = "writer.type";
public static final String AGENT_HOST = "agent.host";
public static final String AGENT_PORT = "agent.port";
public static final String PRIORITY_SAMPLING = "priority.sampling";
public static final String TRACE_RESOLVER_ENABLED = "trace.resolver.enabled";
public static final String SERVICE_MAPPING = "service.mapping";
public static final String SPAN_TAGS = "trace.span.tags";
public static final String HEADER_TAGS = "trace.header.tags";
public static final String JMX_FETCH_ENABLED = "jmxfetch.enabled";
public static final String JMX_FETCH_METRICS_CONFIGS = "jmxfetch.metrics-configs";
public static final String JMX_FETCH_CHECK_PERIOD = "jmxfetch.check-period";
public static final String JMX_FETCH_REFRESH_BEANS_PERIOD = "jmxfetch.refresh-beans-period";
public static final String JMX_FETCH_STATSD_HOST = "jmxfetch.statsd.host";
public static final String JMX_FETCH_STATSD_PORT = "jmxfetch.statsd.port";
public static final String DEFAULT_SERVICE_NAME = "unnamed-java-app";
@ -44,8 +52,11 @@ public class Config {
public static final String DEFAULT_AGENT_HOST = "localhost";
public static final int DEFAULT_AGENT_PORT = 8126;
private static final String DEFAULT_PRIORITY_SAMPLING_ENABLED = "false";
private static final String DEFAULT_TRACE_RESOLVER_ENABLED = "true";
private static final boolean DEFAULT_PRIORITY_SAMPLING_ENABLED = false;
private static final boolean DEFAULT_TRACE_RESOLVER_ENABLED = true;
private static final boolean DEFAULT_JMX_FETCH_ENABLED = false;
public static final int DEFAULT_JMX_FETCH_STATSD_PORT = 8125;
@Getter private final String serviceName;
@Getter private final String writerType;
@ -56,6 +67,12 @@ public class Config {
@Getter private final Map<String, String> serviceMapping;
@Getter private final Map<String, String> spanTags;
@Getter private final Map<String, String> headerTags;
@Getter private final boolean jmxFetchEnabled;
@Getter private final List<String> jmxFetchMetricsConfigs;
@Getter private final Integer jmxFetchCheckPeriod;
@Getter private final Integer jmxFetchRefreshBeansPeriod;
@Getter private final String jmxFetchStatsdHost;
@Getter private final Integer jmxFetchStatsdPort;
// Read order: System Properties -> Env Variables, [-> default value]
// Visible for testing
@ -63,18 +80,23 @@ public class Config {
serviceName = getSettingFromEnvironment(SERVICE_NAME, DEFAULT_SERVICE_NAME);
writerType = getSettingFromEnvironment(WRITER_TYPE, DEFAULT_AGENT_WRITER_TYPE);
agentHost = getSettingFromEnvironment(AGENT_HOST, DEFAULT_AGENT_HOST);
agentPort =
Integer.valueOf(
getSettingFromEnvironment(AGENT_PORT, Integer.toString(DEFAULT_AGENT_PORT)));
agentPort = getIntegerSettingFromEnvironment(AGENT_PORT, DEFAULT_AGENT_PORT);
prioritySamplingEnabled =
Boolean.valueOf(
getSettingFromEnvironment(PRIORITY_SAMPLING, DEFAULT_PRIORITY_SAMPLING_ENABLED));
getBooleanSettingFromEnvironment(PRIORITY_SAMPLING, DEFAULT_PRIORITY_SAMPLING_ENABLED);
traceResolverEnabled =
Boolean.valueOf(
getSettingFromEnvironment(TRACE_RESOLVER_ENABLED, DEFAULT_TRACE_RESOLVER_ENABLED));
getBooleanSettingFromEnvironment(TRACE_RESOLVER_ENABLED, DEFAULT_TRACE_RESOLVER_ENABLED);
serviceMapping = getMapSettingFromEnvironment(SERVICE_MAPPING, null);
spanTags = getMapSettingFromEnvironment(SPAN_TAGS, null);
headerTags = getMapSettingFromEnvironment(HEADER_TAGS, null);
jmxFetchEnabled =
getBooleanSettingFromEnvironment(JMX_FETCH_ENABLED, DEFAULT_JMX_FETCH_ENABLED);
jmxFetchMetricsConfigs = getListSettingFromEnvironment(JMX_FETCH_METRICS_CONFIGS, null);
jmxFetchCheckPeriod = getIntegerSettingFromEnvironment(JMX_FETCH_CHECK_PERIOD, null);
jmxFetchRefreshBeansPeriod =
getIntegerSettingFromEnvironment(JMX_FETCH_REFRESH_BEANS_PERIOD, null);
jmxFetchStatsdHost = getSettingFromEnvironment(JMX_FETCH_STATSD_HOST, null);
jmxFetchStatsdPort =
getIntegerSettingFromEnvironment(JMX_FETCH_STATSD_PORT, DEFAULT_JMX_FETCH_STATSD_PORT);
}
// Read order: Properties -> Parent
@ -82,19 +104,26 @@ public class Config {
serviceName = properties.getProperty(SERVICE_NAME, parent.serviceName);
writerType = properties.getProperty(WRITER_TYPE, parent.writerType);
agentHost = properties.getProperty(AGENT_HOST, parent.agentHost);
agentPort =
Integer.valueOf(properties.getProperty(AGENT_PORT, Integer.toString(parent.agentPort)));
agentPort = getPropertyIntegerValue(properties, AGENT_PORT, parent.agentPort);
prioritySamplingEnabled =
Boolean.valueOf(
properties.getProperty(
PRIORITY_SAMPLING, Boolean.toString(parent.prioritySamplingEnabled)));
getPropertyBooleanValue(properties, PRIORITY_SAMPLING, parent.prioritySamplingEnabled);
traceResolverEnabled =
Boolean.valueOf(
properties.getProperty(
TRACE_RESOLVER_ENABLED, Boolean.toString(parent.traceResolverEnabled)));
getPropertyBooleanValue(properties, TRACE_RESOLVER_ENABLED, parent.traceResolverEnabled);
serviceMapping = getPropertyMapValue(properties, SERVICE_MAPPING, parent.serviceMapping);
spanTags = getPropertyMapValue(properties, SPAN_TAGS, parent.spanTags);
headerTags = getPropertyMapValue(properties, HEADER_TAGS, parent.headerTags);
jmxFetchEnabled =
getPropertyBooleanValue(properties, JMX_FETCH_ENABLED, parent.jmxFetchEnabled);
jmxFetchMetricsConfigs =
getPropertyListValue(properties, JMX_FETCH_METRICS_CONFIGS, parent.jmxFetchMetricsConfigs);
jmxFetchCheckPeriod =
getPropertyIntegerValue(properties, JMX_FETCH_CHECK_PERIOD, parent.jmxFetchCheckPeriod);
jmxFetchRefreshBeansPeriod =
getPropertyIntegerValue(
properties, JMX_FETCH_REFRESH_BEANS_PERIOD, parent.jmxFetchRefreshBeansPeriod);
jmxFetchStatsdHost = properties.getProperty(JMX_FETCH_STATSD_HOST, parent.jmxFetchStatsdHost);
jmxFetchStatsdPort =
getPropertyIntegerValue(properties, JMX_FETCH_STATSD_PORT, parent.jmxFetchStatsdPort);
}
private static String getSettingFromEnvironment(final String name, final String defaultValue) {
@ -110,6 +139,23 @@ public class Config {
return parseMap(getSettingFromEnvironment(name, defaultValue), PREFIX + name);
}
private static List<String> getListSettingFromEnvironment(
final String name, final String defaultValue) {
return parseList(getSettingFromEnvironment(name, defaultValue));
}
private static Boolean getBooleanSettingFromEnvironment(
final String name, final Boolean defaultValue) {
final String value = getSettingFromEnvironment(name, null);
return value == null ? defaultValue : Boolean.valueOf(value);
}
private static Integer getIntegerSettingFromEnvironment(
final String name, final Integer defaultValue) {
final String value = getSettingFromEnvironment(name, null);
return value == null ? defaultValue : Integer.valueOf(value);
}
private static String propertyToEnvironmentName(final String name) {
return name.toUpperCase().replace(".", "_");
}
@ -120,6 +166,24 @@ public class Config {
return value == null ? defaultValue : parseMap(value, name);
}
private static List<String> getPropertyListValue(
final Properties properties, final String name, final List<String> defaultValue) {
final String value = properties.getProperty(name);
return value == null ? defaultValue : parseList(value);
}
private static Boolean getPropertyBooleanValue(
final Properties properties, final String name, final Boolean defaultValue) {
final String value = properties.getProperty(name);
return value == null ? defaultValue : Boolean.valueOf(value);
}
private static Integer getPropertyIntegerValue(
final Properties properties, final String name, final Integer defaultValue) {
final String value = properties.getProperty(name);
return value == null ? defaultValue : Integer.valueOf(value);
}
private static Map<String, String> parseMap(final String str, final String settingName) {
if (str == null || str.trim().isEmpty()) {
return Collections.emptyMap();
@ -148,6 +212,15 @@ public class Config {
return Collections.unmodifiableMap(map);
}
private static List<String> parseList(final String str) {
if (str == null || str.trim().isEmpty()) {
return Collections.emptyList();
}
final String[] tokens = str.split(",", -1);
return Collections.unmodifiableList(Arrays.asList(tokens));
}
public static Config get() {
return INSTANCE;
}

View File

@ -13,6 +13,15 @@ import static Config.SERVICE_MAPPING
import static Config.SERVICE_NAME
import static Config.SPAN_TAGS
import static Config.WRITER_TYPE
import static datadog.trace.api.Config.DEFAULT_JMX_FETCH_STATSD_PORT
import static datadog.trace.api.Config.JMX_FETCH_CHECK_PERIOD
import static datadog.trace.api.Config.JMX_FETCH_ENABLED
import static datadog.trace.api.Config.JMX_FETCH_METRICS_CONFIGS
import static datadog.trace.api.Config.JMX_FETCH_REFRESH_BEANS_PERIOD
import static datadog.trace.api.Config.JMX_FETCH_STATSD_HOST
import static datadog.trace.api.Config.JMX_FETCH_STATSD_PORT
import static datadog.trace.api.Config.PRIORITY_SAMPLING
import static datadog.trace.api.Config.TRACE_RESOLVER_ENABLED
class ConfigTest extends Specification {
@Rule
@ -32,12 +41,20 @@ class ConfigTest extends Specification {
then:
config.serviceName == "unnamed-java-app"
config.serviceMapping == [:]
config.writerType == "DDAgentWriter"
config.prioritySamplingEnabled == false
config.agentHost == "localhost"
config.agentPort == 8126
config.prioritySamplingEnabled == false
config.traceResolverEnabled == true
config.serviceMapping == [:]
config.spanTags == [:]
config.headerTags == [:]
config.jmxFetchEnabled == false
config.jmxFetchMetricsConfigs == []
config.jmxFetchCheckPeriod == null
config.jmxFetchRefreshBeansPeriod == null
config.jmxFetchStatsdHost == null
config.jmxFetchStatsdPort == DEFAULT_JMX_FETCH_STATSD_PORT
config.toString().contains("unnamed-java-app")
}
@ -45,6 +62,19 @@ class ConfigTest extends Specification {
setup:
System.setProperty(PREFIX + SERVICE_NAME, "something else")
System.setProperty(PREFIX + WRITER_TYPE, "LoggingWriter")
System.setProperty(PREFIX + AGENT_HOST, "somehost")
System.setProperty(PREFIX + AGENT_PORT, "123")
System.setProperty(PREFIX + PRIORITY_SAMPLING, "true")
System.setProperty(PREFIX + TRACE_RESOLVER_ENABLED, "false")
System.setProperty(PREFIX + SERVICE_MAPPING, "a:1")
System.setProperty(PREFIX + SPAN_TAGS, "b:2")
System.setProperty(PREFIX + HEADER_TAGS, "c:3")
System.setProperty(PREFIX + JMX_FETCH_ENABLED, "true")
System.setProperty(PREFIX + JMX_FETCH_METRICS_CONFIGS, "/foo.yaml,/bar.yaml")
System.setProperty(PREFIX + JMX_FETCH_CHECK_PERIOD, "100")
System.setProperty(PREFIX + JMX_FETCH_REFRESH_BEANS_PERIOD, "200")
System.setProperty(PREFIX + JMX_FETCH_STATSD_HOST, "statsd host")
System.setProperty(PREFIX + JMX_FETCH_STATSD_PORT, "321")
when:
def config = new Config()
@ -52,6 +82,19 @@ class ConfigTest extends Specification {
then:
config.serviceName == "something else"
config.writerType == "LoggingWriter"
config.agentHost == "somehost"
config.agentPort == 123
config.prioritySamplingEnabled == true
config.traceResolverEnabled == false
config.serviceMapping == [a: "1"]
config.spanTags == [b: "2"]
config.headerTags == [c: "3"]
config.jmxFetchEnabled == true
config.jmxFetchMetricsConfigs == ["/foo.yaml", "/bar.yaml"]
config.jmxFetchCheckPeriod == 100
config.jmxFetchRefreshBeansPeriod == 200
config.jmxFetchStatsdHost == "statsd host"
config.jmxFetchStatsdPort == 321
}
def "specify overrides via env vars"() {
@ -90,22 +133,42 @@ class ConfigTest extends Specification {
def "sys props override properties"() {
setup:
Properties properties = new Properties()
properties.setProperty(SERVICE_NAME, "what we actually want")
properties.setProperty(WRITER_TYPE, "DDAgentWriter")
properties.setProperty(AGENT_HOST, "somewhere")
properties.setProperty(AGENT_PORT, "9999")
properties.setProperty(SERVICE_NAME, "something else")
properties.setProperty(WRITER_TYPE, "LoggingWriter")
properties.setProperty(AGENT_HOST, "somehost")
properties.setProperty(AGENT_PORT, "123")
properties.setProperty(PRIORITY_SAMPLING, "true")
properties.setProperty(TRACE_RESOLVER_ENABLED, "false")
properties.setProperty(SERVICE_MAPPING, "a:1")
properties.setProperty(SPAN_TAGS, "b:2")
properties.setProperty(HEADER_TAGS, "c:3")
properties.setProperty(JMX_FETCH_METRICS_CONFIGS, "/foo.yaml,/bar.yaml")
properties.setProperty(JMX_FETCH_CHECK_PERIOD, "100")
properties.setProperty(JMX_FETCH_REFRESH_BEANS_PERIOD, "200")
properties.setProperty(JMX_FETCH_STATSD_HOST, "statsd host")
properties.setProperty(JMX_FETCH_STATSD_PORT, "321")
when:
def config = Config.get(properties)
then:
config.serviceName == "what we actually want"
config.writerType == "DDAgentWriter"
config.agentHost == "somewhere"
config.agentPort == 9999
config.serviceName == "something else"
config.writerType == "LoggingWriter"
config.agentHost == "somehost"
config.agentPort == 123
config.prioritySamplingEnabled == true
config.traceResolverEnabled == false
config.serviceMapping == [a: "1"]
config.spanTags == [b: "2"]
config.headerTags == [c: "3"]
config.jmxFetchMetricsConfigs == ["/foo.yaml", "/bar.yaml"]
config.jmxFetchCheckPeriod == 100
config.jmxFetchRefreshBeansPeriod == 200
config.jmxFetchStatsdHost == "statsd host"
config.jmxFetchStatsdPort == 321
}
def "sys props override null properties"() {
def "override null properties"() {
when:
def config = Config.get(null)
@ -114,7 +177,7 @@ class ConfigTest extends Specification {
config.writerType == "DDAgentWriter"
}
def "sys props override empty properties"() {
def "override empty properties"() {
setup:
Properties properties = new Properties()
@ -126,6 +189,19 @@ class ConfigTest extends Specification {
config.writerType == "DDAgentWriter"
}
def "override non empty properties"() {
setup:
Properties properties = new Properties()
properties.setProperty("foo", "bar")
when:
def config = Config.get(properties)
then:
config.serviceName == "unnamed-java-app"
config.writerType == "DDAgentWriter"
}
def "verify mapping configs on tracer"() {
setup:
System.setProperty(PREFIX + SERVICE_MAPPING, mapString)
@ -180,4 +256,19 @@ class ConfigTest extends Specification {
mapString | map
null | [:]
}
def "verify empty value list configs on tracer"() {
setup:
System.setProperty(PREFIX + JMX_FETCH_METRICS_CONFIGS, listString)
when:
def config = new Config()
then:
config.jmxFetchMetricsConfigs == list
where:
listString | list
"" | []
}
}

View File

@ -4,6 +4,7 @@ include ':dd-trace-ot'
include ':dd-java-agent'
include ':dd-java-agent:agent-bootstrap'
include ':dd-java-agent:agent-tooling'
include ':dd-java-agent:agent-jmxfetch'
include ':dd-java-agent:testing'
include ':dd-java-agent-ittests'
include ':dd-trace-api'