diff --git a/pom.xml b/pom.xml index 0bdf51bd6..7a7e92f81 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,9 @@ 1.18.38 2.28.2 1.7.32 + + 2.9 + 2.0 1.19.8 @@ -215,6 +218,13 @@ snakeyaml ${snakeyaml.version} + + org.snakeyaml + snakeyaml-engine + ${snakeyaml-engine.version} + provided + true + javax.management.j2ee javax.management.j2ee-api diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 9983267ba..230344cb9 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -84,8 +84,8 @@ public class App { private static int loopCounter; private int lastJsonConfigTs; private Map adJsonConfigs; - private Map configs; - private Map adPipeConfigs = new ConcurrentHashMap<>(); + private Map configs; + private Map adPipeConfigs = new ConcurrentHashMap<>(); private List instances = new ArrayList<>(); private Map brokenInstanceMap = new ConcurrentHashMap<>(); private AtomicBoolean reinit = new AtomicBoolean(false); @@ -403,7 +403,7 @@ public boolean processAutoDiscovery(byte[] buffer) { final String name = getAutoDiscoveryName(config); log.debug("Attempting to apply config. Name: " + name); final InputStream stream = new ByteArrayInputStream(config.getBytes(UTF_8)); - final YamlParser yaml = new YamlParser(stream); + final ConfigYaml yaml = new ConfigYaml(stream); if (this.addConfig(name, yaml)) { reinit = true; @@ -700,7 +700,7 @@ public TaskStatusHandler invoke( * Adds a configuration to the auto-discovery pipe-collected configuration list. This method is * deprecated. */ - public boolean addConfig(final String name, final YamlParser config) { + public boolean addConfig(final String name, final ConfigYaml config) { // named groups not supported with Java6: // // "AUTO_DISCOVERY_PREFIX(?.{1,80})_(?\\d{0,AD_MAX_MAG_INSTANCES})" @@ -746,8 +746,8 @@ public boolean addJsonConfig(final String name, final String json) { return false; } - private Map getConfigs(final AppConfig config) { - final Map configs = new ConcurrentHashMap<>(); + private Map getConfigs(final AppConfig config) { + final Map configs = new ConcurrentHashMap<>(); this.loadFileConfigs(config, configs); this.loadResourceConfigs(config, configs); @@ -756,7 +756,7 @@ private Map getConfigs(final AppConfig config) { return configs; } - private void loadFileConfigs(final AppConfig config, final Map configs) { + private void loadFileConfigs(final AppConfig config, final Map configs) { final List fileList = config.getYamlFileList(); if (fileList != null) { for (final String fileName : fileList) { @@ -765,7 +765,7 @@ private void loadFileConfigs(final AppConfig config, final Map configs) { + final AppConfig config, final Map configs) { final List resourceConfigList = config.getInstanceConfigResources(); if (resourceConfigList != null) { final ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); @@ -788,7 +788,7 @@ private void loadResourceConfigs( log.warn("Cannot find " + resourceName); } else { try { - configs.put(name, new YamlParser(inputStream)); + configs.put(name, new ConfigYaml(inputStream)); } catch (Exception e) { log.warn("Cannot parse yaml file " + resourceName, e); } finally { @@ -933,11 +933,11 @@ public void init(final boolean forceNewConnection) { final Set instanceNamesSeen = new HashSet<>(); log.info("Dealing with YAML config instances..."); - final Iterator> it = this.configs.entrySet().iterator(); - final Iterator> itPipeConfigs = this.adPipeConfigs + final Iterator> it = this.configs.entrySet().iterator(); + final Iterator> itPipeConfigs = this.adPipeConfigs .entrySet().iterator(); while (it.hasNext() || itPipeConfigs.hasNext()) { - Map.Entry entry; + Map.Entry entry; boolean fromPipeIterator = false; if (it.hasNext()) { entry = it.next(); @@ -947,14 +947,14 @@ public void init(final boolean forceNewConnection) { } final String name = entry.getKey(); - final YamlParser yamlConfig = entry.getValue(); + final ConfigYaml yamlConfig = entry.getValue(); // AD config cache doesn't remove configs - it just overwrites. if (!fromPipeIterator) { it.remove(); } final List> configInstances = - ((List>) yamlConfig.getYamlInstances()); + ((List>) yamlConfig.getInstances()); if (configInstances == null || configInstances.size() == 0) { final String warning = "No instance found in :" + name; log.warn(warning); diff --git a/src/main/java/org/datadog/jmxfetch/ConfigYaml.java b/src/main/java/org/datadog/jmxfetch/ConfigYaml.java new file mode 100644 index 000000000..e011098ed --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/ConfigYaml.java @@ -0,0 +1,96 @@ +package org.datadog.jmxfetch; + +import org.datadog.jmxfetch.util.JavaVersion; +import org.snakeyaml.engine.v2.api.Dump; +import org.snakeyaml.engine.v2.api.DumpSettings; +import org.snakeyaml.engine.v2.api.Load; +import org.snakeyaml.engine.v2.api.LoadSettings; +import org.yaml.snakeyaml.Yaml; + +import java.io.InputStream; +import java.util.Map; + +class ConfigYaml { + private static final boolean USE_YAML_ENGINE; + + // used on Java 8+ + private static final ThreadLocal YAML_LOAD; + private static final ThreadLocal YAML_DUMP; + + // used on Java 7 + private static final ThreadLocal YAML_LEGACY; + + static { + ThreadLocal yamlLoad = null; + ThreadLocal yamlDump = null; + + if (JavaVersion.atLeastJava(8)) { + try { + final LoadSettings loadSettings = LoadSettings.builder().build(); + yamlLoad = new ThreadLocal() { + @Override + public Load initialValue() { + return new Load(loadSettings); + } + }; + final DumpSettings dumpSettings = DumpSettings.builder().build(); + yamlDump = new ThreadLocal() { + @Override + public Dump initialValue() { + return new Dump(dumpSettings); + } + }; + } catch (Throwable ignore) { + // snakeyaml-engine not available, fallback to legacy snakeyaml + } + } + + USE_YAML_ENGINE = yamlLoad != null && yamlDump != null; + + if (USE_YAML_ENGINE) { + YAML_LOAD = yamlLoad; + YAML_DUMP = yamlDump; + YAML_LEGACY = null; + } else { + YAML_LOAD = null; + YAML_DUMP = null; + YAML_LEGACY = new ThreadLocal() { + @Override + public Yaml initialValue() { + return new Yaml(); + } + }; + } + } + + private final Map parsedYaml; + + public ConfigYaml(InputStream yamlInputStream) { + parsedYaml = parse(yamlInputStream); + } + + public Object getInstances() { + return parsedYaml.get("instances"); + } + + public Object getInitConfig() { + return parsedYaml.get("init_config"); + } + + @SuppressWarnings("unchecked") + public static T parse(InputStream yamlInputStream) { + if (USE_YAML_ENGINE) { + return (T) YAML_LOAD.get().loadFromInputStream(yamlInputStream); + } else { + return YAML_LEGACY.get().load(yamlInputStream); + } + } + + public static String dump(Object parsedYaml) { + if (USE_YAML_ENGINE) { + return YAML_DUMP.get().dumpToString(parsedYaml); + } else { + return YAML_LEGACY.get().dump(parsedYaml); + } + } +} diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 82570c88e..3a98bfc2b 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -5,9 +5,6 @@ import org.datadog.jmxfetch.service.ConfigServiceNameProvider; import org.datadog.jmxfetch.service.ServiceNameProvider; import org.datadog.jmxfetch.util.InstanceTelemetry; -import org.yaml.snakeyaml.Yaml; - - import java.io.File; import java.io.FileInputStream; @@ -43,14 +40,6 @@ public class Instance { public static final String JVM_DIRECT = "jvm_direct"; public static final String ATTRIBUTE = "Attribute: "; - private static final ThreadLocal YAML = - new ThreadLocal() { - @Override - public Yaml initialValue() { - return new Yaml(); - } - }; - private Set beans; private List beanScopes; private List configurationList = new ArrayList(); @@ -297,8 +286,7 @@ public static boolean isDirectInstance(Map configInstance) { private void loadDefaultConfig(String configResourcePath) { List> defaultConf = - (List>) - YAML.get().load(this.getClass().getResourceAsStream(configResourcePath)); + ConfigYaml.parse(this.getClass().getResourceAsStream(configResourcePath)); for (Map conf : defaultConf) { configurationList.add(new Configuration(conf)); } @@ -321,8 +309,7 @@ static void loadMetricConfigFiles( try { yamlInputStream = new FileInputStream(yamlPath); List> confs = - (List>) - YAML.get().load(yamlInputStream); + ConfigYaml.parse(yamlInputStream); for (Map conf : confs) { configurationList.add(new Configuration(conf)); } @@ -360,8 +347,7 @@ static void loadMetricConfigResources( } else { try { Map>> topYaml = - (Map>>) - YAML.get().load(inputStream); + ConfigYaml.parse(inputStream); List> jmxConf = topYaml.get("jmx_metrics"); if (jmxConf != null) { diff --git a/src/main/java/org/datadog/jmxfetch/Status.java b/src/main/java/org/datadog/jmxfetch/Status.java index 719e56e3d..4b3c71127 100644 --- a/src/main/java/org/datadog/jmxfetch/Status.java +++ b/src/main/java/org/datadog/jmxfetch/Status.java @@ -4,7 +4,6 @@ import org.datadog.jmxfetch.util.InstanceTelemetry; import org.datadog.jmxfetch.util.MetadataHelper; -import org.yaml.snakeyaml.Yaml; import java.io.BufferedOutputStream; import java.io.File; @@ -151,7 +150,7 @@ private String generateYaml() { status.put("timestamp", System.currentTimeMillis()); status.put("checks", this.instanceStats); status.put("errors", this.errors); - return new Yaml().dump(status); + return ConfigYaml.dump(status); } private String generateJson() throws IOException { diff --git a/src/main/java/org/datadog/jmxfetch/YamlParser.java b/src/main/java/org/datadog/jmxfetch/YamlParser.java deleted file mode 100644 index 27a81603f..000000000 --- a/src/main/java/org/datadog/jmxfetch/YamlParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.datadog.jmxfetch; - -import org.yaml.snakeyaml.Yaml; - -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -@SuppressWarnings("unchecked") -class YamlParser { - - private Map parsedYaml; - - public YamlParser(InputStream yamlInputStream) { - parsedYaml = (Map) new Yaml().load(yamlInputStream); - } - - public YamlParser(YamlParser other) { - parsedYaml = new HashMap((Map) other.getParsedYaml()); - } - - public Object getYamlInstances() { - return parsedYaml.get("instances"); - } - - public Object getInitConfig() { - return parsedYaml.get("init_config"); - } - - public Object getParsedYaml() { - return parsedYaml; - } -} diff --git a/src/main/java/org/datadog/jmxfetch/util/JavaVersion.java b/src/main/java/org/datadog/jmxfetch/util/JavaVersion.java new file mode 100644 index 000000000..b63427764 --- /dev/null +++ b/src/main/java/org/datadog/jmxfetch/util/JavaVersion.java @@ -0,0 +1,44 @@ +package org.datadog.jmxfetch.util; + +public final class JavaVersion { + private static final int JAVA_VERSION = getMajorJavaVersion(); + + private JavaVersion() {} + + /** + * Tests whether the Java version is at least the expected version. + * + * @param expectedVersion the expected version + * @return {@code true} if the Java version is at least the expected version + */ + public static boolean atLeastJava(int expectedVersion) { + return expectedVersion <= JAVA_VERSION; + } + + private static int getMajorJavaVersion() { + try { + return parseMajorJavaVersion(System.getProperty("java.version")); + } catch (Throwable e) { + return 7; // assume Java 7, i.e. the lowest version JMXFetch supports + } + } + + private static int parseMajorJavaVersion(String str) { + int value = 0; + for (int i = 0; i < str.length(); i++) { + char ch = str.charAt(i); + if (ch >= '0' && ch <= '9') { + value = value * 10 + (ch - '0'); + } else if (ch == '.') { + if (value == 1) { + value = 0; // skip leading 1. + } else { + break; + } + } else { + throw new NumberFormatException(); + } + } + return value; + } +} diff --git a/src/test/java/org/datadog/jmxfetch/TestConfiguration.java b/src/test/java/org/datadog/jmxfetch/TestConfiguration.java index 0138cf56f..ce8696a9a 100644 --- a/src/test/java/org/datadog/jmxfetch/TestConfiguration.java +++ b/src/test/java/org/datadog/jmxfetch/TestConfiguration.java @@ -49,9 +49,9 @@ public static void init() throws Exception { File f = new File("src/test/resources/", "jmx_bean_scope.yaml"); String yamlPath = f.getAbsolutePath(); FileInputStream yamlInputStream = new FileInputStream(yamlPath); - YamlParser fileConfig = new YamlParser(yamlInputStream); + ConfigYaml fileConfig = new ConfigYaml(yamlInputStream); List> configInstances = - ((List>) fileConfig.getYamlInstances()); + ((List>) fileConfig.getInstances()); for (Map config : configInstances) { Object yamlConf = config.get("conf"); @@ -117,9 +117,9 @@ public void testEmptyInclude() File f = new File("src/test/resources/", "jmx_empty_filters.yaml"); String yamlPath = f.getAbsolutePath(); FileInputStream yamlInputStream = new FileInputStream(yamlPath); - YamlParser fileConfig = new YamlParser(yamlInputStream); + ConfigYaml fileConfig = new ConfigYaml(yamlInputStream); List> configInstances = - ((List>) fileConfig.getYamlInstances()); + ((List>) fileConfig.getInstances()); List confs = new ArrayList(); for (Map config : configInstances) {