From 57d48bdcd9583fdaa268253cabfccda3a662f0aa Mon Sep 17 00:00:00 2001 From: LMnet Date: Thu, 19 Mar 2026 10:27:21 +1300 Subject: [PATCH 1/4] Using getCanonicalName instead of toString for bean names to guarantee the order of key parameters in the bean name --- src/main/java/org/datadog/jmxfetch/App.java | 6 +- .../java/org/datadog/jmxfetch/Instance.java | 72 ++++++++++------- .../org/datadog/jmxfetch/JmxAttribute.java | 7 +- .../datadog/jmxfetch/JmxComplexAttribute.java | 6 +- .../datadog/jmxfetch/JmxSimpleAttribute.java | 6 +- .../org/datadog/jmxfetch/JmxSubAttribute.java | 6 +- .../datadog/jmxfetch/JmxTabularAttribute.java | 6 +- .../java/org/datadog/jmxfetch/TestApp.java | 81 +++++++++++++++++++ .../jmx_bean_regex_canonical_order.yaml | 16 ++++ ...mx_bean_regex_canonical_order_no_flag.yaml | 16 ++++ .../jmx_bean_regex_no_canonical_order.yaml | 16 ++++ 11 files changed, 194 insertions(+), 44 deletions(-) create mode 100644 src/test/resources/jmx_bean_regex_canonical_order.yaml create mode 100644 src/test/resources/jmx_bean_regex_canonical_order_no_flag.yaml create mode 100644 src/test/resources/jmx_bean_regex_no_canonical_order.yaml diff --git a/src/main/java/org/datadog/jmxfetch/App.java b/src/main/java/org/datadog/jmxfetch/App.java index 9983267ba..247f39610 100644 --- a/src/main/java/org/datadog/jmxfetch/App.java +++ b/src/main/java/org/datadog/jmxfetch/App.java @@ -153,7 +153,7 @@ private void registerTelemetryBean(AppTelemetry bean) { | MBeanRegistrationException | NotCompliantMBeanException e) { log.warn("Could not register bean named '{}' for instance: ", - appTelemetryBeanName.toString(), e); + appTelemetryBeanName.getCanonicalName(), e); } } @@ -189,7 +189,7 @@ private void initTelemetryBean() { | MBeanRegistrationException | NotCompliantMBeanException e) { log.warn("Could not register bean named '{}' for instance: ", - appTelemetryBeanName.toString(), e); + appTelemetryBeanName.getCanonicalName(), e); } this.appTelemetry = bean; @@ -208,7 +208,7 @@ private void teardownTelemetry() { log.debug("Successfully unregistered app telemetry bean"); } catch (MBeanRegistrationException | InstanceNotFoundException e) { log.warn("Could not unregister bean named '{}' for instance: ", - appTelemetryBeanName.toString(), e); + appTelemetryBeanName.getCanonicalName(), e); } } diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 82570c88e..84373be79 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -79,6 +79,7 @@ public Yaml initialValue() { private ObjectName instanceTelemetryBeanName; private MBeanServer mbs; private Boolean normalizeBeanParamTags; + private Boolean useCanonicalBeanName; private Map> dynamicTagsCache; /** Constructor, instantiates Instance based of a previous instance and appConfig. */ @@ -202,6 +203,11 @@ public Instance( this.normalizeBeanParamTags = false; } + this.useCanonicalBeanName = (Boolean) instanceMap.get("use_canonical_bean_name"); + if (this.useCanonicalBeanName == null) { + this.useCanonicalBeanName = false; + } + // Alternative aliasing for CASSANDRA-4009 metrics // More information: https://issues.apache.org/jira/browse/CASSANDRA-4009 @@ -278,12 +284,12 @@ private InstanceTelemetry registerTelemetryBean(InstanceTelemetry bean) { try { mbs.registerMBean(bean,instanceTelemetryBeanName); log.debug("Successfully registered jmx bean for instance {} with ObjectName = {}", - this.getName(), instanceTelemetryBeanName); + this.getName(), instanceTelemetryBeanName.getCanonicalName()); } catch (InstanceAlreadyExistsException | MBeanRegistrationException | NotCompliantMBeanException e) { log.warn("Could not register bean named '{}' for instance: ", - instanceTelemetryBeanName.toString(), e); + instanceTelemetryBeanName.getCanonicalName(), e); } return bean; @@ -448,40 +454,40 @@ public void init(boolean forceNewConnection) throws IOException, FailedLoginException, SecurityException { log.info("Trying to connect to JMX Server at " + this.toString()); connection = getConnection(instanceMap, forceNewConnection); - + log.info( "Trying to collect bean list for the first time for JMX Server at {}", this); this.refreshBeansList(); this.initialRefreshTime = this.lastRefreshTime; log.info("Connected to JMX Server at {} with {} beans", this, this.beans.size()); - + // Resolve configuration-level dynamic tags for all configurations // Must be done after refreshBeansList() so the beans exist resolveConfigurationDynamicTags(); - + this.getMatchingAttributes(); log.info("Done initializing JMX Server at {}", this); } - + private void resolveConfigurationDynamicTags() { if (configurationList == null || configurationList.isEmpty()) { return; } - + this.dynamicTagsCache = new HashMap<>(); List allDynamicTags = new ArrayList<>(); - + for (Configuration config : configurationList) { List dynamicTags = config.getDynamicTags(); if (dynamicTags != null && !dynamicTags.isEmpty()) { allDynamicTags.addAll(dynamicTags); } } - + if (allDynamicTags.isEmpty()) { return; } - + int successfulResolutions = 0; for (DynamicTag dynamicTag : allDynamicTags) { String cacheKey = dynamicTag.getBeanAttributeKey(); @@ -495,30 +501,30 @@ private void resolveConfigurationDynamicTags() { successfulResolutions++; } } - - log.info("Resolved {} unique dynamic tag(s) from {} total references for instance {}", + + log.info("Resolved {} unique dynamic tag(s) from {} total references for instance {}", successfulResolutions, allDynamicTags.size(), instanceName); } - + /** * Get resolved dynamic tags for a specific configuration. * This resolves the dynamic tags defined in the configuration using the cached values. - * + * * @param config the configuration to get resolved tags for * @return map of tag name to tag value */ private Map getResolvedDynamicTagsForConfig(Configuration config) { Map resolvedTags = new HashMap<>(); - + if (this.dynamicTagsCache == null || this.dynamicTagsCache.isEmpty()) { return resolvedTags; } - + List dynamicTags = config.getDynamicTags(); if (dynamicTags == null || dynamicTags.isEmpty()) { return resolvedTags; } - + for (DynamicTag dynamicTag : dynamicTags) { String cacheKey = dynamicTag.getBeanAttributeKey(); Map.Entry cached = this.dynamicTagsCache.get(cacheKey); @@ -526,7 +532,7 @@ private Map getResolvedDynamicTagsForConfig(Configuration config resolvedTags.put(cached.getKey(), cached.getValue()); } } - + return resolvedTags; } @@ -652,19 +658,20 @@ private void getMatchingAttributes() throws IOException { } String className; MBeanAttributeInfo[] attributeInfos; + String beanNameStr = beanName.getCanonicalName(); try { - log.debug("Getting bean info for bean: {}", beanName); + log.debug("Getting bean info for bean: {}", beanNameStr); MBeanInfo info = connection.getMBeanInfo(beanName); - log.debug("Getting class name for bean: {}", beanName); + log.debug("Getting class name for bean: {}", beanNameStr); className = info.getClassName(); - log.debug("Getting attributes for bean: {}", beanName); + log.debug("Getting attributes for bean: {}", beanNameStr); attributeInfos = info.getAttributes(); } catch (IOException e) { // we should not continue throw e; } catch (Exception e) { - log.warn("Cannot get attributes or class name for bean {}: ", beanName, e); + log.warn("Cannot get attributes or class name for bean {}: ", beanNameStr, e); continue; } @@ -687,7 +694,7 @@ private void getMatchingAttributes() throws IOException { if (JmxSimpleAttribute.matchAttributeType(attributeType)) { log.debug( ATTRIBUTE - + beanName + + beanNameStr + " : " + attributeInfo + " has attributeInfo simple type"); @@ -703,11 +710,12 @@ private void getMatchingAttributes() throws IOException { tags, cassandraAliasing, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); } else if (JmxComplexAttribute.matchAttributeType(attributeType)) { log.debug( ATTRIBUTE - + beanName + + beanNameStr + " : " + attributeInfo + " has attributeInfo composite type"); @@ -722,11 +730,12 @@ private void getMatchingAttributes() throws IOException { serviceNameProvider, tags, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); } else if (JmxTabularAttribute.matchAttributeType(attributeType)) { log.debug( ATTRIBUTE - + beanName + + beanNameStr + " : " + attributeInfo + " has attributeInfo tabular type"); @@ -741,12 +750,13 @@ private void getMatchingAttributes() throws IOException { serviceNameProvider, tags, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); } else { try { log.debug( ATTRIBUTE - + beanName + + beanNameStr + " : " + attributeInfo + " has an unsupported type: " @@ -764,7 +774,7 @@ private void getMatchingAttributes() throws IOException { for (Configuration conf : configurationList) { try { if (jmxAttribute.match(conf)) { - Map resolvedDynamicTags = + Map resolvedDynamicTags = getResolvedDynamicTagsForConfig(conf); jmxAttribute.setResolvedDynamicTags(resolvedDynamicTags); jmxAttribute.setMatchingConf(conf); @@ -786,7 +796,7 @@ private void getMatchingAttributes() throws IOException { log.error( "Error while trying to match attributeInfo configuration " + "with the Attribute: " - + beanName + + beanNameStr + " : " + attributeInfo, e); diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index d4343e35b..ec2e7deab 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -76,14 +76,17 @@ public abstract class JmxAttribute { Map instanceTags, boolean cassandraAliasing, boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean normalizeBeanParamTags, + boolean useCanonicalBeanName) { this.attribute = attribute; this.beanName = beanName; this.className = className; this.matchingConf = null; this.connection = connection; this.attributeName = attribute.getName(); - this.beanStringName = beanName.toString(); + this.beanStringName = useCanonicalBeanName + ? beanName.getCanonicalName() + : beanName.toString(); this.cassandraAliasing = cassandraAliasing; this.checkName = checkName; this.serviceNameProvider = serviceNameProvider; diff --git a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java index e57cb84f4..819aff8a9 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxComplexAttribute.java @@ -46,7 +46,8 @@ public JmxComplexAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean normalizeBeanParamTags, + boolean useCanonicalBeanName) { super( attribute, beanName, @@ -58,7 +59,8 @@ public JmxComplexAttribute( instanceTags, false, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); } private void populateSubAttributeList(Object attributeValue) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java index c76676ef5..6a331babc 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSimpleAttribute.java @@ -58,7 +58,8 @@ public JmxSimpleAttribute( Map instanceTags, boolean cassandraAliasing, Boolean emptyDefaultHostname, - Boolean normalizeBeanParamTags) { + Boolean normalizeBeanParamTags, + Boolean useCanonicalBeanName) { super( attribute, beanName, @@ -70,7 +71,8 @@ public JmxSimpleAttribute( instanceTags, cassandraAliasing, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); } @Override diff --git a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java index 98a6e1666..7ba48719e 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxSubAttribute.java @@ -22,7 +22,8 @@ public JmxSubAttribute( Map instanceTags, boolean cassandraAliasing, boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean normalizeBeanParamTags, + boolean useCanonicalBeanName) { super( attribute, beanName, @@ -34,7 +35,8 @@ public JmxSubAttribute( instanceTags, cassandraAliasing, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); } public Metric getCachedMetric(String name) { diff --git a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java index 816e0aa29..3bc93bfa4 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxTabularAttribute.java @@ -44,7 +44,8 @@ public JmxTabularAttribute( ServiceNameProvider serviceNameProvider, Map instanceTags, boolean emptyDefaultHostname, - boolean normalizeBeanParamTags) { + boolean normalizeBeanParamTags, + boolean useCanonicalBeanName) { super( attribute, beanName, @@ -56,7 +57,8 @@ public JmxTabularAttribute( instanceTags, false, emptyDefaultHostname, - normalizeBeanParamTags); + normalizeBeanParamTags, + useCanonicalBeanName); subAttributeList = new HashMap>(); } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 1c58ac231..02f81cf5e 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -50,6 +50,87 @@ public void testBeanRegexTags() throws Exception { assertEquals(1, tlm.getRunningInstanceCount()); } + /** Tag metrics with MBean parameters with use_canonical_bean_name option enabled. */ + @Test + public void testBeanRegexUseCanonicalBeanName() throws Exception { + // We expose a metric through JMX with properties in non-alphabetical order + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_canonical_order.yaml"); + + // Collecting metrics + // The bean_regex in the yaml is written in canonical (alphabetical) order: + // name, scope, type — which differs from the registration order above. + // With use_canonical_bean_name: true, the regex matches because JMXFetch + // uses getCanonicalName() to sort properties before matching. + run(); + + List tags = + Arrays.asList( + "type:TestBean", + "name:MyName", + "scope:MyScope", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "dd.internal.jmx_check_name:jmx_bean_regex_canonical_order", + "bean_name:MyName", + "bean_scope:MyScope"); + + assertMetric("this.is.100", tags, 8); + } + + /** Tag metrics with MBean parameters with use_canonical_bean_name option disabled (default). */ + @Test + public void testBeanRegexDontUseCanonicalBeanName() throws Exception { + // We expose a metric through JMX with properties in non-alphabetical order + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_no_canonical_order.yaml"); + + // Collecting metrics + // The bean_regex in the yaml is written in registration order: + // type, name, scope — matching the toString() output. + // With use_canonical_bean_name false, the regex matches + // because JMXFetch uses toString() which preserves registration order. + run(); + + List tags = + Arrays.asList( + "type:TestBean", + "name:MyName", + "scope:MyScope", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "dd.internal.jmx_check_name:jmx_bean_regex_no_canonical_order", + "bean_name:MyName", + "bean_scope:MyScope"); + + assertMetric("this.is.100", tags, 8); + } + + /** Canonical-order regex does not match when use_canonical_bean_name is disabled. */ + @Test + public void testBeanRegexCanonicalOrderNotMatchingWithoutFlag() throws Exception { + // We expose a metric through JMX with properties in non-alphabetical order + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_canonical_order_no_flag.yaml"); + + // Collecting metrics + // The bean_regex is written in canonical (alphabetical) order: name, scope, type. + // But use_canonical_bean_name is false, so JMXFetch matches + // against toString() which preserves registration order: type, name, scope. + // The regex should NOT match — only default java.lang metrics are collected. + run(); + List> metrics = getMetrics(); + + // 13 default metrics from java.lang, no custom metrics matched + assertEquals(13, metrics.size()); + } + /** Tag metrics with MBeans parameters. */ @Test public void testBeanTags() throws Exception { diff --git a/src/test/resources/jmx_bean_regex_canonical_order.yaml b/src/test/resources/jmx_bean_regex_canonical_order.yaml new file mode 100644 index 000000000..5ac5479e5 --- /dev/null +++ b/src/test/resources/jmx_bean_regex_canonical_order.yaml @@ -0,0 +1,16 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + use_canonical_bean_name: true + conf: + - include: + bean_regex: org.datadog.jmxfetch.test:name=(.*),scope=(.*),type=TestBean + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 diff --git a/src/test/resources/jmx_bean_regex_canonical_order_no_flag.yaml b/src/test/resources/jmx_bean_regex_canonical_order_no_flag.yaml new file mode 100644 index 000000000..b1c100b0c --- /dev/null +++ b/src/test/resources/jmx_bean_regex_canonical_order_no_flag.yaml @@ -0,0 +1,16 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + use_canonical_bean_name: false + conf: + - include: + bean_regex: org.datadog.jmxfetch.test:name=(.*),scope=(.*),type=TestBean + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 diff --git a/src/test/resources/jmx_bean_regex_no_canonical_order.yaml b/src/test/resources/jmx_bean_regex_no_canonical_order.yaml new file mode 100644 index 000000000..c0e63a063 --- /dev/null +++ b/src/test/resources/jmx_bean_regex_no_canonical_order.yaml @@ -0,0 +1,16 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + use_canonical_bean_name: false + conf: + - include: + bean_regex: org.datadog.jmxfetch.test:type=TestBean,name=(.*),scope=(.*) + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 From 3b851c33552a2c70ad0386f1f08b402643a1c66a Mon Sep 17 00:00:00 2001 From: LMnet Date: Thu, 26 Mar 2026 11:54:27 +1300 Subject: [PATCH 2/4] Allow use_canonical_bean_name config param to be a part of the init_config configuration --- .../java/org/datadog/jmxfetch/Instance.java | 3 ++ .../java/org/datadog/jmxfetch/TestApp.java | 44 +++++++++++++++++++ ...nonical_init_config_instance_override.yaml | 17 +++++++ ...ean_regex_canonical_order_init_config.yaml | 16 +++++++ 4 files changed, 80 insertions(+) create mode 100644 src/test/resources/jmx_bean_regex_canonical_init_config_instance_override.yaml create mode 100644 src/test/resources/jmx_bean_regex_canonical_order_init_config.yaml diff --git a/src/main/java/org/datadog/jmxfetch/Instance.java b/src/main/java/org/datadog/jmxfetch/Instance.java index 84373be79..530db3ed4 100644 --- a/src/main/java/org/datadog/jmxfetch/Instance.java +++ b/src/main/java/org/datadog/jmxfetch/Instance.java @@ -204,6 +204,9 @@ public Instance( } this.useCanonicalBeanName = (Boolean) instanceMap.get("use_canonical_bean_name"); + if (this.useCanonicalBeanName == null && initConfig != null) { + this.useCanonicalBeanName = (Boolean) initConfig.get("use_canonical_bean_name"); + } if (this.useCanonicalBeanName == null) { this.useCanonicalBeanName = false; } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 02f81cf5e..7213e9291 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -131,6 +131,50 @@ public void testBeanRegexCanonicalOrderNotMatchingWithoutFlag() throws Exception assertEquals(13, metrics.size()); } + /** use_canonical_bean_name in init_config applies to all instances. */ + @Test + public void testBeanRegexCanonicalBeanNameFromInitConfig() throws Exception { + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_canonical_order_init_config.yaml"); + + // bean_regex is in canonical (alphabetical) order: name, scope, type. + // use_canonical_bean_name: true is set in init_config, not on the instance. + // The regex should match because the init_config value is used as fallback. + run(); + + List tags = + Arrays.asList( + "type:TestBean", + "scope:MyScope", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "dd.internal.jmx_check_name:jmx_bean_regex_canonical_order_init_config", + "bean_name:MyName", + "bean_scope:MyScope"); + + assertMetric("this.is.100", tags, 8); + } + + /** Instance-level use_canonical_bean_name overrides init_config. */ + @Test + public void testBeanRegexCanonicalBeanNameInstanceOverridesInitConfig() throws Exception { + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_canonical_init_config_instance_override.yaml"); + + // init_config sets use_canonical_bean_name: true, but instance sets it to false. + // bean_regex is in canonical order, which won't match toString() output. + // Instance value wins, so the regex should NOT match. + run(); + List> metrics = getMetrics(); + + // 13 default metrics from java.lang, no custom metrics matched + assertEquals(13, metrics.size()); + } + /** Tag metrics with MBeans parameters. */ @Test public void testBeanTags() throws Exception { diff --git a/src/test/resources/jmx_bean_regex_canonical_init_config_instance_override.yaml b/src/test/resources/jmx_bean_regex_canonical_init_config_instance_override.yaml new file mode 100644 index 000000000..d941176ad --- /dev/null +++ b/src/test/resources/jmx_bean_regex_canonical_init_config_instance_override.yaml @@ -0,0 +1,17 @@ +init_config: + use_canonical_bean_name: true + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + use_canonical_bean_name: false + conf: + - include: + bean_regex: org.datadog.jmxfetch.test:name=(.*),scope=(.*),type=TestBean + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 diff --git a/src/test/resources/jmx_bean_regex_canonical_order_init_config.yaml b/src/test/resources/jmx_bean_regex_canonical_order_init_config.yaml new file mode 100644 index 000000000..af8aa5ced --- /dev/null +++ b/src/test/resources/jmx_bean_regex_canonical_order_init_config.yaml @@ -0,0 +1,16 @@ +init_config: + use_canonical_bean_name: true + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + conf: + - include: + bean_regex: org.datadog.jmxfetch.test:name=(.*),scope=(.*),type=TestBean + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 From b92024446167c8c4c650ab211b03a40c230b0b1e Mon Sep 17 00:00:00 2001 From: LMnet Date: Thu, 26 Mar 2026 14:32:58 +1300 Subject: [PATCH 3/4] Support filter-level use_canonical_bean_name flag --- .../java/org/datadog/jmxfetch/Filter.java | 5 +++ .../org/datadog/jmxfetch/JmxAttribute.java | 34 +++++++++++--- .../java/org/datadog/jmxfetch/TestApp.java | 45 +++++++++++++++++++ ...x_canonical_filter_overrides_instance.yaml | 17 +++++++ .../jmx_bean_regex_canonical_in_filter.yaml | 17 +++++++ 5 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 src/test/resources/jmx_bean_regex_canonical_filter_overrides_instance.yaml create mode 100644 src/test/resources/jmx_bean_regex_canonical_in_filter.yaml diff --git a/src/main/java/org/datadog/jmxfetch/Filter.java b/src/main/java/org/datadog/jmxfetch/Filter.java index eb886ed94..4affc2fb7 100644 --- a/src/main/java/org/datadog/jmxfetch/Filter.java +++ b/src/main/java/org/datadog/jmxfetch/Filter.java @@ -185,6 +185,11 @@ public List getParameterValues(String parameterName) { return toStringList(beanValues); } + /** Per-filter override for canonical bean name matching. Null means defer to instance default. */ + public Boolean getUseCanonicalBeanName() { + return (Boolean) filter.get("use_canonical_bean_name"); + } + public boolean isEmptyBeanName() { return (filter.get("bean") == null && filter.get("bean_name") == null); } diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index ec2e7deab..6b9973013 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -39,7 +39,8 @@ public abstract class JmxAttribute { "class_regex", "attribute", "exclude_tags", - "tags"); + "tags", + "use_canonical_bean_name"); private static final String FIRST_CAP_PATTERN = "(.)([A-Z][a-z]+)"; private static final String ALL_CAP_PATTERN = "([a-z0-9])([A-Z])"; private static final String METRIC_REPLACEMENT = "([^a-zA-Z0-9_.]+)|(^[^a-zA-Z]+)"; @@ -52,6 +53,8 @@ public abstract class JmxAttribute { private String domain; private String className; private String beanStringName; + private String canonicalBeanStringName; + private String toStringBeanStringName; private ServiceNameProvider serviceNameProvider; private Map beanParameters; private String attributeName; @@ -84,9 +87,11 @@ public abstract class JmxAttribute { this.matchingConf = null; this.connection = connection; this.attributeName = attribute.getName(); + this.canonicalBeanStringName = beanName.getCanonicalName(); + this.toStringBeanStringName = beanName.toString(); this.beanStringName = useCanonicalBeanName - ? beanName.getCanonicalName() - : beanName.toString(); + ? canonicalBeanStringName + : toStringBeanStringName; this.cassandraAliasing = cassandraAliasing; this.checkName = checkName; this.serviceNameProvider = serviceNameProvider; @@ -143,7 +148,7 @@ private void addAdditionalTags() { } } } - + /** Add dynamic tags that were resolved at connection time. */ private void addDynamicTags() { if (this.resolvedDynamicTags != null && !this.resolvedDynamicTags.isEmpty()) { @@ -407,8 +412,9 @@ private boolean matchBeanRegex(Filter filter, boolean matchIfNoRegex) { return matchIfNoRegex; } + String nameToMatch = resolveBeanStringName(filter); for (Pattern beanRegex : beanRegexes) { - Matcher matcher = beanRegex.matcher(beanStringName); + Matcher matcher = beanRegex.matcher(nameToMatch); if (matcher.matches()) { for (int i = 0; i <= matcher.groupCount(); i++) { @@ -422,8 +428,9 @@ private boolean matchBeanRegex(Filter filter, boolean matchIfNoRegex) { private boolean matchBeanName(Configuration configuration) { Filter include = configuration.getInclude(); + String nameToMatch = resolveBeanStringName(include); - if (!include.isEmptyBeanName() && !include.getBeanNames().contains(beanStringName)) { + if (!include.isEmptyBeanName() && !include.getBeanNames().contains(nameToMatch)) { return false; } @@ -445,8 +452,9 @@ private boolean matchBeanName(Configuration configuration) { private boolean excludeMatchBeanName(Configuration conf) { Filter exclude = conf.getExclude(); List beanNames = exclude.getBeanNames(); + String nameToMatch = resolveBeanStringName(exclude); - if (beanNames.contains(beanStringName)) { + if (beanNames.contains(nameToMatch)) { return true; } @@ -670,6 +678,18 @@ String getBeanStringName() { return beanStringName; } + /** + * Resolve bean name string for matching against a specific filter. + * Filter-level use_canonical_bean_name overrides the instance-level default. + */ + private String resolveBeanStringName(Filter filter) { + Boolean filterUseCanonicalBeanNameFlag = filter.getUseCanonicalBeanName(); + if (filterUseCanonicalBeanNameFlag != null) { + return filterUseCanonicalBeanNameFlag ? canonicalBeanStringName : toStringBeanStringName; + } + return beanStringName; + } + String getAttributeName() { return attributeName; } diff --git a/src/test/java/org/datadog/jmxfetch/TestApp.java b/src/test/java/org/datadog/jmxfetch/TestApp.java index 7213e9291..08503a920 100644 --- a/src/test/java/org/datadog/jmxfetch/TestApp.java +++ b/src/test/java/org/datadog/jmxfetch/TestApp.java @@ -175,6 +175,51 @@ public void testBeanRegexCanonicalBeanNameInstanceOverridesInitConfig() throws E assertEquals(13, metrics.size()); } + /** use_canonical_bean_name in include filter enables canonical matching for that filter. */ + @Test + public void testBeanRegexCanonicalBeanNameInFilter() throws Exception { + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_canonical_in_filter.yaml"); + + // init_config.use_canonical_bean_name = false . + // The include filter sets use_canonical_bean_name: true. + // bean_regex is in canonical (alphabetical) order: name, scope, type. + // Filter-level override should kick in, so the regex matches. + run(); + + List tags = + Arrays.asList( + "type:TestBean", + "scope:MyScope", + "instance:jmx_test_instance", + "jmx_domain:org.datadog.jmxfetch.test", + "dd.internal.jmx_check_name:jmx_bean_regex_canonical_in_filter", + "bean_name:MyName", + "bean_scope:MyScope"); + + assertMetric("this.is.100", tags, 8); + } + + /** use_canonical_bean_name: false in include filter overrides instance-level true. */ + @Test + public void testBeanRegexCanonicalFilterOverridesInstance() throws Exception { + registerMBean( + new SimpleTestJavaApp(), + "org.datadog.jmxfetch.test:type=TestBean,name=MyName,scope=MyScope"); + initApplication("jmx_bean_regex_canonical_filter_overrides_instance.yaml"); + + // Instance sets use_canonical_bean_name: true, but the include filter sets it to false. + // bean_regex is in canonical order, which won't match toString() output. + // Filter-level value wins, so the regex should NOT match. + run(); + List> metrics = getMetrics(); + + // 13 default metrics from java.lang, no custom metrics matched + assertEquals(13, metrics.size()); + } + /** Tag metrics with MBeans parameters. */ @Test public void testBeanTags() throws Exception { diff --git a/src/test/resources/jmx_bean_regex_canonical_filter_overrides_instance.yaml b/src/test/resources/jmx_bean_regex_canonical_filter_overrides_instance.yaml new file mode 100644 index 000000000..09463de06 --- /dev/null +++ b/src/test/resources/jmx_bean_regex_canonical_filter_overrides_instance.yaml @@ -0,0 +1,17 @@ +init_config: + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + use_canonical_bean_name: true + conf: + - include: + use_canonical_bean_name: false + bean_regex: org.datadog.jmxfetch.test:name=(.*),scope=(.*),type=TestBean + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 diff --git a/src/test/resources/jmx_bean_regex_canonical_in_filter.yaml b/src/test/resources/jmx_bean_regex_canonical_in_filter.yaml new file mode 100644 index 000000000..4945944bf --- /dev/null +++ b/src/test/resources/jmx_bean_regex_canonical_in_filter.yaml @@ -0,0 +1,17 @@ +init_config: + use_canonical_bean_name: false + +instances: + - process_name_regex: .*surefire.* + name: jmx_test_instance + conf: + - include: + use_canonical_bean_name: true + bean_regex: org.datadog.jmxfetch.test:name=(.*),scope=(.*),type=TestBean + attribute: + ShouldBe100: + metric_type: gauge + alias: this.is.100 + tags: + bean_name: $1 + bean_scope: $2 From d69f94e89e6da5bb86125bb560b2f486c2d798eb Mon Sep 17 00:00:00 2001 From: LMnet Date: Tue, 31 Mar 2026 10:11:52 +1300 Subject: [PATCH 4/4] linting fix --- src/main/java/org/datadog/jmxfetch/Filter.java | 5 ++++- src/main/java/org/datadog/jmxfetch/JmxAttribute.java | 6 +++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/datadog/jmxfetch/Filter.java b/src/main/java/org/datadog/jmxfetch/Filter.java index 4affc2fb7..c9ac4388b 100644 --- a/src/main/java/org/datadog/jmxfetch/Filter.java +++ b/src/main/java/org/datadog/jmxfetch/Filter.java @@ -185,7 +185,10 @@ public List getParameterValues(String parameterName) { return toStringList(beanValues); } - /** Per-filter override for canonical bean name matching. Null means defer to instance default. */ + /** + * Per-filter override for canonical bean name matching. + * Null means defer to instance default. + */ public Boolean getUseCanonicalBeanName() { return (Boolean) filter.get("use_canonical_bean_name"); } diff --git a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java index 6b9973013..ef87b9b67 100644 --- a/src/main/java/org/datadog/jmxfetch/JmxAttribute.java +++ b/src/main/java/org/datadog/jmxfetch/JmxAttribute.java @@ -685,7 +685,11 @@ String getBeanStringName() { private String resolveBeanStringName(Filter filter) { Boolean filterUseCanonicalBeanNameFlag = filter.getUseCanonicalBeanName(); if (filterUseCanonicalBeanNameFlag != null) { - return filterUseCanonicalBeanNameFlag ? canonicalBeanStringName : toStringBeanStringName; + if (filterUseCanonicalBeanNameFlag) { + return canonicalBeanStringName; + } else { + return toStringBeanStringName; + } } return beanStringName; }