diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ea7e27816e..d83de18775 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -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 diff --git a/apm-agent-builds/apm-agent-java8/pom.xml b/apm-agent-builds/apm-agent-java8/pom.xml index 2ccc31a115..c2e4c18bf2 100644 --- a/apm-agent-builds/apm-agent-java8/pom.xml +++ b/apm-agent-builds/apm-agent-java8/pom.xml @@ -21,7 +21,7 @@ org.apache.logging.log4j log4j-bom - 2.21.0 + 2.21.1 import pom diff --git a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxConfiguration.java b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxConfiguration.java index e33848c0e2..e1026b4e8c 100644 --- a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxConfiguration.java +++ b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxConfiguration.java @@ -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" + diff --git a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetric.java b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetric.java index 7427494bad..9fa1b4da6d 100644 --- a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetric.java +++ b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetric.java @@ -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 @@ -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; } } diff --git a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java index 300cbb22de..685eb59992 100644 --- a/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java +++ b/apm-agent-plugins/apm-jmx-plugin/src/main/java/co/elastic/apm/agent/jmx/JmxMetricTracker.java @@ -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; @@ -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; @@ -284,7 +287,7 @@ private void register(List 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 compileJmxMetricRegistrations(List jmxMetrics, MBeanServer server) { List registrations = new ArrayList<>(); @@ -308,40 +311,98 @@ private static void addJmxMetricRegistration(final JmxMetric jmxMetric, List 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 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; diff --git a/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricTrackerTest.java b/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricTrackerTest.java index 17fa88bae5..cebd3c78c5 100644 --- a/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricTrackerTest.java +++ b/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricTrackerTest.java @@ -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]")); diff --git a/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricValueConverterTest.java b/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricValueConverterTest.java index 8f92231a9e..9cc2c106a5 100644 --- a/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricValueConverterTest.java +++ b/apm-agent-plugins/apm-jmx-plugin/src/test/java/co/elastic/apm/agent/jmx/JmxMetricValueConverterTest.java @@ -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); }); } diff --git a/apm-agent-plugins/apm-micrometer-plugin/pom.xml b/apm-agent-plugins/apm-micrometer-plugin/pom.xml index 688d25c9a3..318fa15616 100644 --- a/apm-agent-plugins/apm-micrometer-plugin/pom.xml +++ b/apm-agent-plugins/apm-micrometer-plugin/pom.xml @@ -29,7 +29,7 @@ io.micrometer micrometer-core - 1.11.5 + 1.12.0 provided diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/SpringAmqpBatchIT.java b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/SpringAmqpBatchIT.java index 776a7f786e..d6b00fcfe0 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/SpringAmqpBatchIT.java +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/SpringAmqpBatchIT.java @@ -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; @@ -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 @@ -158,7 +157,7 @@ public void testTransactionPerBatch() { .filter(span -> Objects.equals(span.getNameAsString(), "RabbitMQ SEND to ")) .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()); }); @@ -193,3 +192,4 @@ public void testTransactionPerBatch() { rootTraceTransaction.deactivate().end(); } } + diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/components/batch/BatchListenerComponent.java b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/components/batch/BatchListenerComponent.java new file mode 100644 index 0000000000..90e160aae1 --- /dev/null +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/components/batch/BatchListenerComponent.java @@ -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 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() { + } +} diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BaseConfiguration.java b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BaseConfiguration.java index 9c9f8e9efe..863ec3f7cc 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BaseConfiguration.java +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BaseConfiguration.java @@ -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; diff --git a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BatchConfiguration.java b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BatchConfiguration.java index 8d8db7c3a4..eef1d4dcaa 100644 --- a/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BatchConfiguration.java +++ b/apm-agent-plugins/apm-rabbitmq/apm-rabbitmq-spring/src/test/java/co/elastic/apm/agent/rabbitmq/config/BatchConfiguration.java @@ -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; @@ -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 @@ -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 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(); - }); - } } diff --git a/catalog-info.yaml b/catalog-info.yaml index 6730746268..c7c179d53b 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -27,6 +27,8 @@ spec: access_level: MANAGE_BUILD_AND_READ observablt-robots: access_level: MANAGE_BUILD_AND_READ + observablt-robots-automation: + access_level: BUILD_AND_READ everyone: access_level: READ_ONLY @@ -59,6 +61,8 @@ spec: access_level: MANAGE_BUILD_AND_READ observablt-robots: access_level: MANAGE_BUILD_AND_READ + observablt-robots-automation: + access_level: BUILD_AND_READ everyone: access_level: READ_ONLY @@ -91,6 +95,8 @@ spec: access_level: MANAGE_BUILD_AND_READ observablt-robots: access_level: MANAGE_BUILD_AND_READ + observablt-robots-automation: + access_level: BUILD_AND_READ everyone: access_level: READ_ONLY @@ -124,6 +130,8 @@ spec: access_level: MANAGE_BUILD_AND_READ observablt-robots: access_level: MANAGE_BUILD_AND_READ + observablt-robots-automation: + access_level: BUILD_AND_READ everyone: access_level: READ_ONLY schedules: diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 6a40c95213..3c3b2fe75d 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -2030,8 +2030,9 @@ Note that all JMX metric names will be prefixed with `jvm.jmx.` by the agent. 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`. -The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards. -In this example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`) +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 +The definition `object_name[*:type=*,name=*] attribute[*]` would match all possible JMX metrics +In the following example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`) ---- object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime] @@ -4326,8 +4327,9 @@ Example: `5ms`. # # 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`. # -# The link:https://docs.oracle.com/javase/7/docs/api/javax/management/ObjectName.html[JMX object name pattern] supports wildcards. -# In this example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`) +# 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 +# The definition `object_name[*:type=*,name=*] attribute[*]` would match all possible JMX metrics +# In the following example, the agent will create a metricset for each memory pool `name` (such as `G1 Old Generation` and `G1 Young Generation`) # # ---- # object_name[java.lang:type=GarbageCollector,name=*] attribute[CollectionCount:metric_name=collection_count] attribute[CollectionTime]