Merge pull request #505 from DataDog/mar-kolya/jmxfetch
Use JMXFetch as a library
This commit is contained in:
commit
c0e4c7bad2
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
|
@ -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 == []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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'
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
init_config:
|
||||
is_jmx: true
|
||||
|
||||
instances:
|
||||
- jmx_url: service:jmx:local:///
|
||||
conf:
|
||||
# Intentionally left empty for now
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
"" | []
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
Loading…
Reference in New Issue