Skip to content

Commit

Permalink
Merge branch 'main' of github.com:elastic/apm-agent-java into gardene…
Browse files Browse the repository at this point in the history
…r-meta-parsing
  • Loading branch information
SylvainJuge committed Nov 20, 2023
2 parents 4b9f3d4 + 3e2ec51 commit 9233fcc
Show file tree
Hide file tree
Showing 14 changed files with 186 additions and 61 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
* Added support for AWS SDK 2.21 - {pull}3373[#3373]
* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bueckt`, `aws.s3.key` - {pull}3364[#3364]
* Added `context_propagation_only` configuration option - {pull}3358[#3358]
* Added attribute[*] for JMX pattern metrics (all metrics can now be generated with `object_name[*:type=*,name=*] attribute[*]`) - {pull}3376[#3376]
[float]
===== Bug fixes
Expand Down
2 changes: 1 addition & 1 deletion apm-agent-builds/apm-agent-java8/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<!-- Upgrade log4j2 to non vulnerable version -->
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-bom</artifactId>
<version>2.21.0</version>
<version>2.21.1</version>
<scope>import</scope>
<type>pom</type>
</dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ public class JmxConfiguration extends ConfigurationOptionProvider {
"\n" +
"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" +
"\n" +
"The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards.\n" +
"In this example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`)\n" +
"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" +
"The definition `object_name[*:type=*,name=*] attribute[*]` would match all possible JMX metrics\n" +
"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" +
"\n" +
"----\n" +
"object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,12 @@ public String getJmxAttributeName() {
return jmxAttributeName;
}

public String getMetricName() {
return JMX_PREFIX + (metricName != null ? metricName : jmxAttributeName);
public String getMetricName(String foundAttributeName) {
if (!foundAttributeName.isEmpty() && "*".equals(jmxAttributeName)) {
return JMX_PREFIX + (metricName != null ? metricName : foundAttributeName);
} else {
return JMX_PREFIX + (metricName != null ? metricName : jmxAttributeName);
}
}

@Override
Expand All @@ -211,8 +215,8 @@ public Labels getLabels(ObjectName objectName) {
return Labels.Mutable.of(objectName.getKeyPropertyList());
}

public String getCompositeMetricName(String key) {
return getMetricName() + "." + key;
public String getCompositeMetricName(String key, String foundAttributeName) {
return getMetricName(foundAttributeName) + "." + key;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerFactory;
Expand All @@ -42,6 +44,7 @@
import javax.management.NotificationListener;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.RuntimeMBeanException;
import javax.management.openmbean.CompositeData;
import javax.management.relation.MBeanServerNotificationFilter;
import java.lang.management.ManagementFactory;
Expand Down Expand Up @@ -284,7 +287,7 @@ private void register(List<JmxMetric> jmxMetrics, MBeanServer server) {
}

/**
* A single {@link JmxMetric} can yield multiple {@link JmxMetricRegistration}s if the {@link JmxMetric} contains multiple {@link JmxMetric#attributes}
* A single {@link JmxMetric} can yield multiple {@link JmxMetricRegistration}s if the {@link JmxMetric} contains multiple attributes
*/
private List<JmxMetricRegistration> compileJmxMetricRegistrations(List<JmxMetric> jmxMetrics, MBeanServer server) {
List<JmxMetricRegistration> registrations = new ArrayList<>();
Expand All @@ -308,40 +311,98 @@ private static void addJmxMetricRegistration(final JmxMetric jmxMetric, List<Jmx
for (ObjectInstance mbean : mbeans) {
for (JmxMetric.Attribute attribute : jmxMetric.getAttributes()) {
final ObjectName objectName = mbean.getObjectName();
final Object value;
try {
value = server.getAttribute(objectName, attribute.getJmxAttributeName());
if (value instanceof Number) {
logger.debug("Found number attribute {}={}", attribute.getJmxAttributeName(), value);
registrations.add(new JmxMetricRegistration(attribute.getMetricName(),
attribute.getLabels(objectName),
attribute.getJmxAttributeName(),
null,
objectName));
} else if (value instanceof CompositeData) {
final CompositeData compositeValue = (CompositeData) value;
for (final String key : compositeValue.getCompositeType().keySet()) {
if (compositeValue.get(key) instanceof Number) {
logger.debug("Found composite number attribute {}.{}={}", attribute.getJmxAttributeName(), key, value);
registrations.add(new JmxMetricRegistration(attribute.getCompositeMetricName(key),
attribute.getLabels(objectName),
attribute.getJmxAttributeName(),
key,
objectName));
final String metricPrepend = metricPrepend(attribute.getLabels(objectName));
if (isWildcard(attribute)) {
MBeanInfo info = server.getMBeanInfo(objectName);
MBeanAttributeInfo[] attrInfo = info.getAttributes();
for (MBeanAttributeInfo attr : attrInfo) {
try {
final Object value = server.getAttribute(objectName, attr.getName());
addJmxMetricRegistration(jmxMetric, registrations, objectName, value, attribute, attr.getName(), metricPrepend);
} catch (AttributeNotFoundException e) {
logger.warn("Can't create metric '{}' because attribute '{}' could not be found", jmxMetric, attribute.getJmxAttributeName());
} catch (RuntimeMBeanException e) {
if (e.getCause() instanceof UnsupportedOperationException) {
//ignore this attribute
} else {
logger.warn("Can't create metric '{}' because composite value '{}' is not a number: '{}'", jmxMetric, key, value);
throw e;
}
}
} else {
logger.warn("Can't create metric '{}' because attribute '{}' is not a number: '{}'", jmxMetric, attribute.getJmxAttributeName(), value);
}
} catch (AttributeNotFoundException e) {
logger.warn("Can't create metric '{}' because attribute '{}' could not be found", jmxMetric, attribute.getJmxAttributeName());
} else {
final Object value = server.getAttribute(objectName, attribute.getJmxAttributeName());
try {
addJmxMetricRegistration(jmxMetric, registrations, objectName, value, attribute, attribute.getJmxAttributeName(), null);
} catch (AttributeNotFoundException e) {
logger.warn("Can't create metric '{}' because attribute '{}' could not be found", jmxMetric, attribute.getJmxAttributeName());
}
}
}
}
}

private static boolean isWildcard(JmxMetric.Attribute attribute) {
return "*".equals(attribute.getJmxAttributeName());
}

private static String metricPrepend(Labels labels) {
List<String> keys = labels.getKeys();
for (int i = 0; i < keys.size(); i++) {
if ("type".equals(keys.get(i))) {
return labels.getValue(i) + ".";
}
}
return "";
}

private static void addJmxMetricRegistration(JmxMetric jmxMetric, List<JmxMetricRegistration> registrations, ObjectName objectName, Object value, JmxMetric.Attribute attribute, String attributeName, String metricPrepend) throws AttributeNotFoundException {
if (value instanceof Number) {
logger.debug("Found number attribute {}={}", attribute.getJmxAttributeName(), value);
registrations.add(
new JmxMetricRegistration(
attribute.getMetricName(
metricPrepend == null ?
attributeName :
metricPrepend + attributeName
),
attribute.getLabels(objectName),
attributeName,
null,
objectName
)
);
} else if (value instanceof CompositeData) {
final CompositeData compositeValue = (CompositeData) value;
for (final String key : compositeValue.getCompositeType().keySet()) {
if (compositeValue.get(key) instanceof Number) {
logger.debug("Found composite number attribute {}.{}={}", attribute.getJmxAttributeName(), key, value);
registrations.add(
new JmxMetricRegistration(
attribute.getCompositeMetricName(
key,
metricPrepend == null ?
attributeName :
metricPrepend + attributeName
),
attribute.getLabels(objectName),
attributeName,
key,
objectName
)
);
} else {
if (!isWildcard(attribute)) {
logger.warn("Can't create metric '{}' because composite value '{}' is not a number: '{}'", jmxMetric, key, value);
}
}
}
} else {
if (!isWildcard(attribute)) {
logger.warn("Can't create metric '{}' because attribute '{}' is not a number: '{}'", jmxMetric, attributeName, value);
}
}
}

static class JmxMetricRegistration {
private static final Logger logger = LoggerFactory.getLogger(JmxMetricRegistration.class);
private final String metricName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ void testGC() throws Exception {
printMetricSets();
}

@Test
void testAttributeWildcard() throws Exception {
setConfig(JmxMetric.valueOf("object_name[java.lang:type=GarbageCollector,name=*] attribute[*]"));
for (GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) {
String memoryManagerName = gcBean.getName();
assertThat(metricRegistry.getGaugeValue("jvm.jmx.collection_count", Labels.Mutable.of("name", memoryManagerName).add("type", "GarbageCollector"))).isNotNegative();
assertThat(metricRegistry.getGaugeValue("jvm.jmx.CollectionTime", Labels.Mutable.of("name", memoryManagerName).add("type", "GarbageCollector"))).isNotNegative();
}
printMetricSets();
}

@Test
void testRemoveMetric() throws Exception {
setConfig(JmxMetric.valueOf("object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ private static void testMetricName(String s, String expectedMetricName, String e
JmxMetric metric = JmxMetric.valueOf(s);
assertThat(metric.getAttributes()).hasSize(1);
metric.getAttributes().forEach(a -> {
assertThat(a.getMetricName()).isEqualTo(expectedMetricName);
assertThat(a.getMetricName("")).isEqualTo(expectedMetricName);
assertThat(a.getJmxAttributeName()).isEqualTo(expectedJmxAttributeName);
});
}
Expand Down
2 changes: 1 addition & 1 deletion apm-agent-plugins/apm-micrometer-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.11.5</version>
<version>1.12.0</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
package co.elastic.apm.agent.rabbitmq;


import co.elastic.apm.agent.tracer.configuration.MessagingConfiguration;
import co.elastic.apm.agent.impl.transaction.Span;
import co.elastic.apm.agent.impl.transaction.TraceContext;
import co.elastic.apm.agent.impl.transaction.Transaction;
import co.elastic.apm.agent.rabbitmq.components.batch.BatchListenerComponent;
import co.elastic.apm.agent.rabbitmq.config.BatchConfiguration;
import org.junit.Ignore;
import co.elastic.apm.agent.tracer.configuration.MessagingConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.BatchingRabbitTemplate;
Expand All @@ -42,10 +42,9 @@
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.mockito.Mockito.doReturn;

@Ignore
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = {BatchConfiguration.class}, initializers = {RabbitMqTestBase.Initializer.class})
@ContextConfiguration(classes = {BatchConfiguration.class, BatchListenerComponent.class}, initializers = {RabbitMqTestBase.Initializer.class})
public class SpringAmqpBatchIT extends RabbitMqTestBase {

@Autowired
Expand Down Expand Up @@ -158,7 +157,7 @@ public void testTransactionPerBatch() {
.filter(span -> Objects.equals(span.getNameAsString(), "RabbitMQ SEND to <default>"))
.collect(Collectors.toList());
assertThat(sendSpans.size()).isEqualTo(2);
sendSpans.forEach(span -> {
sendSpans.forEach(span -> {
assertThat(span.getType()).isEqualTo("messaging");
assertThat(span.getTraceContext().getParentId()).isEqualTo(rootTraceTransaction.getTraceContext().getId());
});
Expand Down Expand Up @@ -193,3 +192,4 @@ public void testTransactionPerBatch() {
rootTraceTransaction.deactivate().end();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.apm.agent.rabbitmq.components.batch;

import co.elastic.apm.agent.rabbitmq.TestConstants;
import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import co.elastic.apm.api.CaptureSpan;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.List;

import static co.elastic.apm.agent.rabbitmq.TestConstants.QUEUE_NAME;

@Component
public class BatchListenerComponent {

public static final Logger logger = LoggerFactory.getLogger(BatchListenerComponent.class);

@RabbitListener(
queues = TestConstants.QUEUE_NAME,
containerFactory = "simpleRabbitListenerContainerFactory"
)
public void receiveWorkingBatch(List<Message> batchMessages) {
logger.info("Received batch of size {} from '{}'", batchMessages.size(), QUEUE_NAME);
batchMessages.forEach(message -> {
logger.info("Message in 'spring-boot' batch: {}", message.getBody());
testSpan();
});
}

@CaptureSpan(value = "testSpan", type = "custom", subtype = "anything", action = "test")
public void testSpan() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.env.Environment;
import org.springframework.web.client.RestTemplate;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@
package co.elastic.apm.agent.rabbitmq.config;


import co.elastic.apm.agent.rabbitmq.TestConstants;
import co.elastic.apm.agent.sdk.logging.Logger;
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.annotation.EnableRabbit;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.batch.SimpleBatchingStrategy;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
Expand All @@ -37,8 +34,6 @@
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.List;

import static co.elastic.apm.agent.rabbitmq.TestConstants.QUEUE_NAME;

@EnableRabbit
Expand Down Expand Up @@ -83,16 +78,4 @@ public Jackson2JsonMessageConverter converter() {
public Queue queue() {
return new Queue(QUEUE_NAME, false);
}

@RabbitListener(
queues = TestConstants.QUEUE_NAME,
containerFactory = "simpleRabbitListenerContainerFactory"
)
public void receiveWorkingBatch(List<Message> batchMessages) {
logger.info("Received batch of size {} from '{}'", batchMessages.size(), QUEUE_NAME);
batchMessages.forEach(message -> {
logger.info("Message in 'spring-boot' batch: {}", message.getBody());
testSpan();
});
}
}
Loading

0 comments on commit 9233fcc

Please sign in to comment.