Skip to content

Commit 5ff1430

Browse files
authored
add support for attribute[*] with capture_jmx_metrics (#3376)
* add support for attribute[*] with capture_jmx_metrics * fixx doc and remove comment * expand wildcard import, refactor wildcard check, format better * add changelog entry for attribute[*] * fix changelog typo * add 'from' version for feature
1 parent a1272aa commit 5ff1430

File tree

7 files changed

+117
-37
lines changed

7 files changed

+117
-37
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
3737
* Added support for AWS SDK 2.21 - {pull}3373[#3373]
3838
* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bueckt`, `aws.s3.key` - {pull}3364[#3364]
3939
* Added `context_propagation_only` configuration option - {pull}3358[#3358]
40+
* Added attribute[*] for JMX pattern metrics (all metrics can now be generated with `object_name[*:type=*,name=*] attribute[*]`) - {pull}3376[#3376]
4041
4142
[float]
4243
===== Bug fixes

apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ public class JmxConfiguration extends ConfigurationOptionProvider {
5858
"\n" +
5959
"The agent creates `labels` for each link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html#getKeyPropertyList()[JMX key property] such as `type` and `name`.\n" +
6060
"\n" +
61-
"The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards.\n" +
62-
"In this example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)\n" +
61+
"The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards. The attribute definition does NOT support wildcards, but a special definition `attribute[*]` is accepted (from 1.44.0) to mean match all possible (numeric) attributes for the associated object name pattern\n" +
62+
"The definition `object_name[*:type=*,name=*] attribute[*]` would match all possible JMX metrics\n" +
63+
"In the following example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)\n" +
6364
"\n" +
6465
"----\n" +
6566
"object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]\n" +

apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetric.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,12 @@ public String getJmxAttributeName() {
184184
return jmxAttributeName;
185185
}
186186

187-
public String getMetricName() {
188-
return JMX_PREFIX + (metricName != null ? metricName : jmxAttributeName);
187+
public String getMetricName(String foundAttributeName) {
188+
if (!foundAttributeName.isEmpty() && "*".equals(jmxAttributeName)) {
189+
return JMX_PREFIX + (metricName != null ? metricName : foundAttributeName);
190+
} else {
191+
return JMX_PREFIX + (metricName != null ? metricName : jmxAttributeName);
192+
}
189193
}
190194

191195
@Override
@@ -211,8 +215,8 @@ public Labels getLabels(ObjectName objectName) {
211215
return Labels.Mutable.of(objectName.getKeyPropertyList());
212216
}
213217

214-
public String getCompositeMetricName(String key) {
215-
return getMetricName() + "." + key;
218+
public String getCompositeMetricName(String key, String foundAttributeName) {
219+
return getMetricName(foundAttributeName) + "." + key;
216220
}
217221
}
218222

apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java

Lines changed: 87 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import javax.management.AttributeNotFoundException;
3434
import javax.management.InstanceNotFoundException;
3535
import javax.management.JMException;
36+
import javax.management.MBeanAttributeInfo;
37+
import javax.management.MBeanInfo;
3638
import javax.management.MBeanServer;
3739
import javax.management.MBeanServerDelegate;
3840
import javax.management.MBeanServerFactory;
@@ -42,6 +44,7 @@
4244
import javax.management.NotificationListener;
4345
import javax.management.ObjectInstance;
4446
import javax.management.ObjectName;
47+
import javax.management.RuntimeMBeanException;
4548
import javax.management.openmbean.CompositeData;
4649
import javax.management.relation.MBeanServerNotificationFilter;
4750
import java.lang.management.ManagementFactory;
@@ -284,7 +287,7 @@ private void register(List<JmxMetric> jmxMetrics, MBeanServer server) {
284287
}
285288

286289
/**
287-
* A single {@link JmxMetric} can yield multiple {@link JmxMetricRegistration}s if the {@link JmxMetric} contains multiple {@link JmxMetric#attributes}
290+
* A single {@link JmxMetric} can yield multiple {@link JmxMetricRegistration}s if the {@link JmxMetric} contains multiple attributes
288291
*/
289292
private List<JmxMetricRegistration> compileJmxMetricRegistrations(List<JmxMetric> jmxMetrics, MBeanServer server) {
290293
List<JmxMetricRegistration> registrations = new ArrayList<>();
@@ -308,40 +311,98 @@ private static void addJmxMetricRegistration(final JmxMetric jmxMetric, List<Jmx
308311
for (ObjectInstance mbean : mbeans) {
309312
for (JmxMetric.Attribute attribute : jmxMetric.getAttributes()) {
310313
final ObjectName objectName = mbean.getObjectName();
311-
final Object value;
312-
try {
313-
value = server.getAttribute(objectName, attribute.getJmxAttributeName());
314-
if (value instanceof Number) {
315-
logger.debug("Found number attribute {}={}", attribute.getJmxAttributeName(), value);
316-
registrations.add(new JmxMetricRegistration(attribute.getMetricName(),
317-
attribute.getLabels(objectName),
318-
attribute.getJmxAttributeName(),
319-
null,
320-
objectName));
321-
} else if (value instanceof CompositeData) {
322-
final CompositeData compositeValue = (CompositeData) value;
323-
for (final String key : compositeValue.getCompositeType().keySet()) {
324-
if (compositeValue.get(key) instanceof Number) {
325-
logger.debug("Found composite number attribute {}.{}={}", attribute.getJmxAttributeName(), key, value);
326-
registrations.add(new JmxMetricRegistration(attribute.getCompositeMetricName(key),
327-
attribute.getLabels(objectName),
328-
attribute.getJmxAttributeName(),
329-
key,
330-
objectName));
314+
final String metricPrepend = metricPrepend(attribute.getLabels(objectName));
315+
if (isWildcard(attribute)) {
316+
MBeanInfo info = server.getMBeanInfo(objectName);
317+
MBeanAttributeInfo[] attrInfo = info.getAttributes();
318+
for (MBeanAttributeInfo attr : attrInfo) {
319+
try {
320+
final Object value = server.getAttribute(objectName, attr.getName());
321+
addJmxMetricRegistration(jmxMetric, registrations, objectName, value, attribute, attr.getName(), metricPrepend);
322+
} catch (AttributeNotFoundException e) {
323+
logger.warn("Can't create metric '{}' because attribute '{}' could not be found", jmxMetric, attribute.getJmxAttributeName());
324+
} catch (RuntimeMBeanException e) {
325+
if (e.getCause() instanceof UnsupportedOperationException) {
326+
//ignore this attribute
331327
} else {
332-
logger.warn("Can't create metric '{}' because composite value '{}' is not a number: '{}'", jmxMetric, key, value);
328+
throw e;
333329
}
334330
}
335-
} else {
336-
logger.warn("Can't create metric '{}' because attribute '{}' is not a number: '{}'", jmxMetric, attribute.getJmxAttributeName(), value);
337331
}
338-
} catch (AttributeNotFoundException e) {
339-
logger.warn("Can't create metric '{}' because attribute '{}' could not be found", jmxMetric, attribute.getJmxAttributeName());
332+
} else {
333+
final Object value = server.getAttribute(objectName, attribute.getJmxAttributeName());
334+
try {
335+
addJmxMetricRegistration(jmxMetric, registrations, objectName, value, attribute, attribute.getJmxAttributeName(), null);
336+
} catch (AttributeNotFoundException e) {
337+
logger.warn("Can't create metric '{}' because attribute '{}' could not be found", jmxMetric, attribute.getJmxAttributeName());
338+
}
340339
}
341340
}
342341
}
343342
}
344343

344+
private static boolean isWildcard(JmxMetric.Attribute attribute) {
345+
return "*".equals(attribute.getJmxAttributeName());
346+
}
347+
348+
private static String metricPrepend(Labels labels) {
349+
List<String> keys = labels.getKeys();
350+
for (int i = 0; i < keys.size(); i++) {
351+
if ("type".equals(keys.get(i))) {
352+
return labels.getValue(i) + ".";
353+
}
354+
}
355+
return "";
356+
}
357+
358+
private static void addJmxMetricRegistration(JmxMetric jmxMetric, List<JmxMetricRegistration> registrations, ObjectName objectName, Object value, JmxMetric.Attribute attribute, String attributeName, String metricPrepend) throws AttributeNotFoundException {
359+
if (value instanceof Number) {
360+
logger.debug("Found number attribute {}={}", attribute.getJmxAttributeName(), value);
361+
registrations.add(
362+
new JmxMetricRegistration(
363+
attribute.getMetricName(
364+
metricPrepend == null ?
365+
attributeName :
366+
metricPrepend + attributeName
367+
),
368+
attribute.getLabels(objectName),
369+
attributeName,
370+
null,
371+
objectName
372+
)
373+
);
374+
} else if (value instanceof CompositeData) {
375+
final CompositeData compositeValue = (CompositeData) value;
376+
for (final String key : compositeValue.getCompositeType().keySet()) {
377+
if (compositeValue.get(key) instanceof Number) {
378+
logger.debug("Found composite number attribute {}.{}={}", attribute.getJmxAttributeName(), key, value);
379+
registrations.add(
380+
new JmxMetricRegistration(
381+
attribute.getCompositeMetricName(
382+
key,
383+
metricPrepend == null ?
384+
attributeName :
385+
metricPrepend + attributeName
386+
),
387+
attribute.getLabels(objectName),
388+
attributeName,
389+
key,
390+
objectName
391+
)
392+
);
393+
} else {
394+
if (!isWildcard(attribute)) {
395+
logger.warn("Can't create metric '{}' because composite value '{}' is not a number: '{}'", jmxMetric, key, value);
396+
}
397+
}
398+
}
399+
} else {
400+
if (!isWildcard(attribute)) {
401+
logger.warn("Can't create metric '{}' because attribute '{}' is not a number: '{}'", jmxMetric, attributeName, value);
402+
}
403+
}
404+
}
405+
345406
static class JmxMetricRegistration {
346407
private static final Logger logger = LoggerFactory.getLogger(JmxMetricRegistration.class);
347408
private final String metricName;

apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricTrackerTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,17 @@ void testGC() throws Exception {
8383
printMetricSets();
8484
}
8585

86+
@Test
87+
void testAttributeWildcard() throws Exception {
88+
setConfig(JmxMetric.valueOf("object_name[java.lang:type=GarbageCollector,name=*] attribute[*]"));
89+
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
90+
String memoryManagerName = gcBean.getName();
91+
assertThat(metricRegistry.getGaugeValue("jvm.jmx.collection_count", Labels.Mutable.of("name", memoryManagerName).add("type", "GarbageCollector"))).isNotNegative();
92+
assertThat(metricRegistry.getGaugeValue("jvm.jmx.CollectionTime", Labels.Mutable.of("name", memoryManagerName).add("type", "GarbageCollector"))).isNotNegative();
93+
}
94+
printMetricSets();
95+
}
96+
8697
@Test
8798
void testRemoveMetric() throws Exception {
8899
setConfig(JmxMetric.valueOf("object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]"));

apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricValueConverterTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ private static void testMetricName(String s, String expectedMetricName, String e
4040
JmxMetric metric = JmxMetric.valueOf(s);
4141
assertThat(metric.getAttributes()).hasSize(1);
4242
metric.getAttributes().forEach(a -> {
43-
assertThat(a.getMetricName()).isEqualTo(expectedMetricName);
43+
assertThat(a.getMetricName("")).isEqualTo(expectedMetricName);
4444
assertThat(a.getJmxAttributeName()).isEqualTo(expectedJmxAttributeName);
4545
});
4646
}

docs/configuration.asciidoc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,8 +2030,9 @@ Note that all JMX metric names will be prefixed with `jvm.jmx.` by the agent.
20302030

20312031
The agent creates `labels` for each link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html#getKeyPropertyList()[JMX key property] such as `type` and `name`.
20322032

2033-
The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards.
2034-
In this example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)
2033+
The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards. The attribute definition does NOT support wildcards, but a special definition `attribute[*]` is accepted (from 1.44.0) to mean match all possible (numeric) attributes for the associated object name pattern
2034+
The definition `object_name[*:type=*,name=*] attribute[*]` would match all possible JMX metrics
2035+
In the following example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)
20352036

20362037
----
20372038
object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]
@@ -4326,8 +4327,9 @@ Example: `5ms`.
43264327
#
43274328
# The agent creates `labels` for each link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html#getKeyPropertyList()[JMX key property] such as `type` and `name`.
43284329
#
4329-
# The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards.
4330-
# In this example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)
4330+
# The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards. The attribute definition does NOT support wildcards, but a special definition `attribute[*]` is accepted (from 1.44.0) to mean match all possible (numeric) attributes for the associated object name pattern
4331+
# The definition `object_name[*:type=*,name=*] attribute[*]` would match all possible JMX metrics
4332+
# In the following example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)
43314333
#
43324334
# ----
43334335
# object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]

0 commit comments

Comments
 (0)