diff --git a/instrumentation/jmx-metrics/README.md b/instrumentation/jmx-metrics/README.md index 0ac449a32f7a..b429ca5ea03a 100644 --- a/instrumentation/jmx-metrics/README.md +++ b/instrumentation/jmx-metrics/README.md @@ -25,7 +25,7 @@ $ java -javaagent:path/to/opentelemetry-javaagent.jar \ No targets are enabled by default. The supported target environments are listed below. -- [activemq](javaagent/activemq.md) +- [activemq](library/activemq.md) - [camel](javaagent/camel.md) - [jetty](library/jetty.md) - [kafka-broker](javaagent/kafka-broker.md) diff --git a/instrumentation/jmx-metrics/javaagent/activemq.md b/instrumentation/jmx-metrics/javaagent/activemq.md deleted file mode 100644 index 49a01985291e..000000000000 --- a/instrumentation/jmx-metrics/javaagent/activemq.md +++ /dev/null @@ -1,17 +0,0 @@ -# ActiveMQ Metrics - -Here is the list of metrics based on MBeans exposed by ActiveMQ. - -| Metric Name | Type | Attributes | Description | -| -------------------------------------------- | ------------- | ------------------- | --------------------------------------------------------------------- | -| activemq.ProducerCount | UpDownCounter | destination, broker | The number of producers attached to this destination | -| activemq.ConsumerCount | UpDownCounter | destination, broker | The number of consumers subscribed to this destination | -| activemq.memory.MemoryPercentUsage | Gauge | destination, broker | The percentage of configured memory used | -| activemq.message.QueueSize | UpDownCounter | destination, broker | The current number of messages waiting to be consumed | -| activemq.message.ExpiredCount | Counter | destination, broker | The number of messages not delivered because they expired | -| activemq.message.EnqueueCount | Counter | destination, broker | The number of messages sent to this destination | -| activemq.message.DequeueCount | Counter | destination, broker | The number of messages acknowledged and removed from this destination | -| activemq.message.AverageEnqueueTime | Gauge | destination, broker | The average time a message was held on this destination | -| activemq.connections.CurrentConnectionsCount | UpDownCounter | | The total number of current connections | -| activemq.disc.StorePercentUsage | Gauge | | The percentage of configured disk used for persistent messages | -| activemq.disc.TempPercentUsage | Gauge | | The percentage of configured disk used for non-persistent messages | diff --git a/instrumentation/jmx-metrics/javaagent/src/main/resources/jmx/rules/activemq.yaml b/instrumentation/jmx-metrics/javaagent/src/main/resources/jmx/rules/activemq.yaml deleted file mode 100644 index c7d669d55282..000000000000 --- a/instrumentation/jmx-metrics/javaagent/src/main/resources/jmx/rules/activemq.yaml +++ /dev/null @@ -1,67 +0,0 @@ ---- -rules: - - beans: - - org.apache.activemq:type=Broker,brokerName=*,destinationType=Queue,destinationName=* - - org.apache.activemq:type=Broker,brokerName=*,destinationType=Topic,destinationName=* - metricAttribute: - destination: param(destinationName) - broker: param(brokerName) - prefix: activemq. - mapping: - ProducerCount: - unit: "{producers}" - type: updowncounter - desc: The number of producers attached to this destination - ConsumerCount: - unit: "{consumers}" - type: updowncounter - desc: The number of consumers subscribed to this destination - MemoryPercentUsage: - metric: memory.MemoryPercentUsage - unit: "%" - type: gauge - desc: The percentage of configured memory used - QueueSize: - metric: message.QueueSize - unit: "{messages}" - type: updowncounter - desc: The current number of messages waiting to be consumed - ExpiredCount: - metric: message.ExpiredCount - unit: "{messages}" - type: counter - desc: The number of messages not delivered because they expired - EnqueueCount: - metric: message.EnqueueCount - unit: "{messages}" - type: counter - desc: The number of messages sent to this destination - DequeueCount: - metric: message.DequeueCount - unit: "{messages}" - type: counter - desc: The number of messages acknowledged and removed from this destination - AverageEnqueueTime: - metric: message.AverageEnqueueTime - unit: ms - type: gauge - desc: The average time a message was held on this destination - - - bean: org.apache.activemq:type=Broker,brokerName=* - metricAttribute: - broker: param(brokerName) - prefix: activemq. - unit: "%" - type: gauge - mapping: - CurrentConnectionsCount: - metric: connections.CurrentConnectionsCount - type: updowncounter - unit: "{connections}" - desc: The total number of current connections - StorePercentUsage: - metric: disc.StorePercentUsage - desc: The percentage of configured disk used for persistent messages - TempPercentUsage: - metric: disc.TempPercentUsage - desc: The percentage of configured disk used for non-persistent messages diff --git a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java index 2f486ade2e54..2f90a285b302 100644 --- a/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java +++ b/instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java @@ -31,7 +31,7 @@ class JmxMetricInsightInstallerTest { private static final String PATH_TO_ALL_EXISTING_RULES = "src/main/resources/jmx/rules"; private static final Set FILES_TO_BE_TESTED = - new HashSet<>(Arrays.asList("activemq.yaml", "camel.yaml", "kafka-broker.yaml")); + new HashSet<>(Arrays.asList("camel.yaml", "kafka-broker.yaml")); @Test void testToVerifyExistingRulesAreValid() throws Exception { diff --git a/instrumentation/jmx-metrics/library/activemq.md b/instrumentation/jmx-metrics/library/activemq.md new file mode 100644 index 000000000000..db17c8fa2d3b --- /dev/null +++ b/instrumentation/jmx-metrics/library/activemq.md @@ -0,0 +1,32 @@ +# ActiveMQ Metrics + +Here is the list of metrics based on MBeans exposed by ActiveMQ. + +For now, only ActiveMQ classic is supported. + +| Metric Name | Type | Unit | Attributes | Description | +|-------------------------------------------|---------------|--------------|-----------------------------------------------------------------------------|-----------------------------------------------------------------------| +| activemq.producer.count | UpDownCounter | {producer} | messaging.destination.name, activemq.broker.name, activemq.destination.type | The number of producers attached to this destination | +| activemq.consumer.count | UpDownCounter | {consumer} | messaging.destination.name, activemq.broker.name, activemq.destination.type | The number of consumers subscribed to this destination | +| activemq.destination.memory.usage | UpDownCounter | By | messaging.destination.name, activemq.broker.name, activemq.destination.type | The amount of used memory by this destination | +| activemq.destination.memory.limit | UpDownCounter | By | messaging.destination.name, activemq.broker.name, activemq.destination.type | The amount of configured memory limit for this destination | +| activemq.destination.temp.utilization | Gauge | 1 | messaging.destination.name, activemq.broker.name, activemq.destination.type | The fraction of non-persistent storage used by this destination | +| activemq.destination.temp.limit | UpDownCounter | By | messaging.destination.name, activemq.broker.name, activemq.destination.type | The amount of configured non-persistent storage limit | +| activemq.message.queue.size | UpDownCounter | {message} | messaging.destination.name, activemq.broker.name, activemq.destination.type | The current number of messages waiting to be consumed | +| activemq.message.expired | Counter | {message} | messaging.destination.name, activemq.broker.name, activemq.destination.type | The number of messages not delivered because they expired | +| activemq.message.enqueued | Counter | {message} | messaging.destination.name, activemq.broker.name, activemq.destination.type | The number of messages sent to this destination | +| activemq.message.dequeued | Counter | {message} | messaging.destination.name, activemq.broker.name, activemq.destination.type | The number of messages acknowledged and removed from this destination | +| activemq.message.enqueue.average_duration | Gauge | s | messaging.destination.name, activemq.broker.name, activemq.destination.type | The average time a message was held on this destination | +| activemq.connection.count | UpDownCounter | {connection} | activemq.broker.name | The number of active connections | +| activemq.memory.utilization | Gauge | 1 | activemq.broker.name | The fraction of broker memory used | +| activemq.memory.limit | UpDownCounter | By | activemq.broker.name | The amount of configured broker memory limit | +| activemq.store.utilization | Gauge | 1 | activemq.broker.name | The fraction of broker persistent storage used | +| activemq.store.limit | UpDownCounter | By | activemq.broker.name | The amount of configured broker persistent storage limit | +| activemq.temp.utilization | Gauge | 1 | activemq.broker.name | The fraction of broker non-persistent storage used | +| activemq.temp.limit | UpDownCounter | By | activemq.broker.name | The amount of configured broker non-persistent storage limit | + +## Attributes + +- `messaging.destination.name` contains the destination.name ([semconv](https://opentelemetry.io/docs/specs/semconv/registry/attributes/messaging/#messaging-destination-name)) +- `activemq.broker.name` contains the name of the broker +- `activemq.destination.type` is set to `queue` for queues (point-to-point), `topic` for topics (multicast). diff --git a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverter.java b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverter.java index 8e4fbbf4da3f..0e3d3e8d405e 100644 --- a/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverter.java +++ b/instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverter.java @@ -22,6 +22,7 @@ class UnitConverter { registerConversion("ms", "s", value -> value.doubleValue() / TimeUnit.SECONDS.toMillis(1)); registerConversion("us", "s", value -> value.doubleValue() / TimeUnit.SECONDS.toMicros(1)); registerConversion("ns", "s", value -> value.doubleValue() / TimeUnit.SECONDS.toNanos(1)); + registerConversion("%", "1", value -> value.doubleValue() / 100d); } private final Function convertingFunction; diff --git a/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/activemq.yaml b/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/activemq.yaml new file mode 100644 index 000000000000..217cf40d11c8 --- /dev/null +++ b/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/activemq.yaml @@ -0,0 +1,131 @@ +--- +rules: + + # Topic and Queues level metrics + - bean: org.apache.activemq:type=Broker,brokerName=*,destinationType=*,destinationName=* + metricAttribute: + messaging.destination.name: param(destinationName) + # 'topic' or 'queue' + activemq.destination.type: lowercase(param(destinationType)) + activemq.broker.name: param(brokerName) + prefix: activemq. + mapping: + # activemq.producer.count + ProducerCount: + metric: producer.count + unit: "{producer}" + type: updowncounter + desc: The number of producers attached to this destination + # activemq.consumer.count + ConsumerCount: + metric: consumer.count + unit: "{consumer}" + type: updowncounter + desc: The number of consumers subscribed to this destination + # activemq.destination.memory.usage + MemoryUsageByteCount: + metric: destination.memory.usage + unit: By + type: updowncounter + desc: The amount of used memory by this destination + # activemq.destination.memory.limit + MemoryLimit: + metric: destination.memory.limit + unit: By + type: updowncounter + desc: The amount of configured memory limit for this destination + # activemq.destination.temp.utilization + TempUsagePercentUsage: + metric: destination.temp.utilization + unit: "1" + type: gauge + desc: The percentage of non-persistent storage used by this destination + # activemq.destination.temp.limit + TempUsageLimit: + metric: destination.temp.limit + unit: By + type: updowncounter + desc: The amount of configured non-persistent storage limit for this destination + # activemq.message.queue.size + QueueSize: + metric: message.queue.size + unit: "{message}" + type: updowncounter + desc: The current number of messages waiting to be consumed + # activemq.message.expired + ExpiredCount: + metric: message.expired + unit: "{message}" + type: counter + desc: The number of messages not delivered because they expired + # activemq.message.enqueued + EnqueueCount: + metric: message.enqueued + unit: "{message}" + type: counter + desc: The number of messages sent to this destination + # activemq.message.dequeued + DequeueCount: + metric: message.dequeued + unit: "{message}" + type: counter + desc: The number of messages acknowledged and removed from this destination + # activemq.message.enqueue.average_duration + AverageEnqueueTime: + metric: message.enqueue.average_duration + sourceUnit: ms + unit: s + type: gauge + desc: The average time a message was held on this destination + + # Broker-level metrics + - bean: org.apache.activemq:type=Broker,brokerName=* + metricAttribute: + activemq.broker.name: param(brokerName) + prefix: activemq. + mapping: + # activemq.connection.count + CurrentConnectionsCount: + metric: connection.count + type: updowncounter + unit: "{connection}" + desc: The number of active connections + # activemq.memory.utilization + MemoryPercentUsage: + metric: memory.utilization + type: gauge + sourceUnit: "%" + unit: "1" + desc: The percentage of broker memory used + # activemq.memory.limit + MemoryLimit: + metric: memory.limit + type: updowncounter + unit: By + desc: The amount of configured broker memory limit + # activemq.store.utilization + StorePercentUsage: + metric: store.utilization + type: gauge + sourceUnit: "%" + unit: "1" + desc: The percentage of broker persistent storage used + # activemq.store.limit + StoreLimit: + metric: store.limit + type: updowncounter + unit: By + desc: The amount of configured broker persistent storage limit + # activemq.temp.utilization + TempPercentUsage: + metric: temp.utilization + type: gauge + sourceUnit: "%" + unit: "1" + desc: The percentage of broker non-persistent storage used + # activemq.temp.limit + TempLimit: + metric: temp.limit + type: updowncounter + unit: By + desc: The amount of configured broker non-persistent storage limit diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverterTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverterTest.java index 4221059b443e..13f6eec4fbca 100644 --- a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverterTest.java +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/engine/UnitConverterTest.java @@ -32,15 +32,26 @@ class UnitConverterTest { }) void shouldSupportPredefined_to_s_Conversions( Long originalValue, String originalUnit, Double expectedConvertedValue) { - // Given - String targetUnit = "s"; + testConversion(originalValue, originalUnit, expectedConvertedValue, "s"); + } - // When + @ParameterizedTest + @CsvSource({ + "100,1.0", "99,0.99", "1,0.01", "0,0", + }) + void shouldSupportPredefined_percent_Conversions( + Long originalValue, Double expectedConvertedValue) { + testConversion(originalValue, "%", expectedConvertedValue, "1"); + } + + private static void testConversion( + Long originalValue, String originalUnit, Double expectedConvertedValue, String targetUnit) { UnitConverter converter = UnitConverter.getInstance(originalUnit, targetUnit); + + assertThat(converter).isNotNull(); Number actualValue = converter.convert(originalValue); - // Then - assertEquals(expectedConvertedValue, actualValue); + assertThat(expectedConvertedValue).isEqualTo(actualValue); } @ParameterizedTest diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/rules/ActiveMqTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/rules/ActiveMqTest.java new file mode 100644 index 000000000000..1612868927b5 --- /dev/null +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/rules/ActiveMqTest.java @@ -0,0 +1,219 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.jmx.rules; + +import static io.opentelemetry.instrumentation.jmx.rules.assertions.DataPointAttributes.attribute; +import static io.opentelemetry.instrumentation.jmx.rules.assertions.DataPointAttributes.attributeGroup; + +import io.opentelemetry.instrumentation.jmx.rules.assertions.AttributeMatcher; +import io.opentelemetry.instrumentation.jmx.rules.assertions.AttributeMatcherGroup; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; + +public class ActiveMqTest extends TargetSystemTest { + + private static final int ACTIVEMQ_PORT = 61616; + + @Test + void activemqTest() { + List yamlFiles = Collections.singletonList("activemq.yaml"); + + yamlFiles.forEach(this::validateYamlSyntax); + + List jvmArgs = new ArrayList<>(); + jvmArgs.add(javaAgentJvmArgument()); + jvmArgs.addAll(javaPropertiesToJvmArgs(otelConfigProperties(yamlFiles))); + + GenericContainer target = + new GenericContainer<>( + new ImageFromDockerfile() + .withDockerfileFromBuilder( + builder -> builder.from("apache/activemq-classic:6.1.7").build())) + .withEnv("JAVA_TOOL_OPTIONS", String.join(" ", jvmArgs)) + .withStartupTimeout(Duration.ofMinutes(2)) + .withExposedPorts(ACTIVEMQ_PORT) + .waitingFor(Wait.forListeningPorts(ACTIVEMQ_PORT)); + + copyAgentToTarget(target); + copyYamlFilesToTarget(target, yamlFiles); + + startTarget(target); + + verifyMetrics(createMetricsVerifier()); + } + + private static MetricsVerifier createMetricsVerifier() { + + // known attributes from the single topic available by default + AttributeMatcher destinationName = + attribute("messaging.destination.name", "ActiveMQ.Advisory.MasterBroker"); + AttributeMatcher topic = attribute("activemq.destination.type", "topic"); + AttributeMatcher broker = attribute("activemq.broker.name", "localhost"); + + AttributeMatcherGroup topicAttributes = attributeGroup(destinationName, topic, broker); + AttributeMatcherGroup brokerAttributes = attributeGroup(broker); + + return MetricsVerifier.create() + // consumers and producers + .add( + "activemq.producer.count", + metric -> + metric + .isUpDownCounter() + .hasUnit("{producer}") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The number of producers attached to this destination")) + .add( + "activemq.consumer.count", + metric -> + metric + .isUpDownCounter() + .hasUnit("{consumer}") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The number of consumers subscribed to this destination")) + // message consumption and in-flight + .add( + "activemq.message.queue.size", + metric -> + metric + .isUpDownCounter() + .hasUnit("{message}") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The current number of messages waiting to be consumed")) + .add( + "activemq.message.expired", + metric -> + metric + .isCounter() + .hasUnit("{message}") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The number of messages not delivered because they expired")) + .add( + "activemq.message.enqueued", + metric -> + metric + .isCounter() + .hasUnit("{message}") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The number of messages sent to this destination")) + .add( + "activemq.message.dequeued", + metric -> + metric + .isCounter() + .hasUnit("{message}") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription( + "The number of messages acknowledged and removed from this destination")) + .add( + "activemq.message.enqueue.average_duration", + metric -> + metric + .isGauge() + .hasUnit("s") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The average time a message was held on this destination")) + // destination memory/temp usage and limits + .add( + "activemq.destination.memory.usage", + metric -> + metric + .isUpDownCounter() + .hasUnit("By") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The amount of used memory by this destination")) + .add( + "activemq.destination.memory.limit", + metric -> + metric + .isUpDownCounter() + .hasUnit("By") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription("The amount of configured memory limit for this destination")) + .add( + "activemq.destination.temp.utilization", + metric -> + metric + .isGauge() + .hasUnit("1") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription( + "The percentage of non-persistent storage used by this destination")) + .add( + "activemq.destination.temp.limit", + metric -> + metric + .isUpDownCounter() + .hasUnit("By") + .hasDataPointsWithAttributes(topicAttributes) + .hasDescription( + "The amount of configured non-persistent storage limit for this destination")) + // broker metrics + .add( + "activemq.connection.count", + metric -> + metric + .isUpDownCounter() + .hasUnit("{connection}") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription("The number of active connections")) + .add( + "activemq.memory.utilization", + metric -> + metric + .isGauge() + .hasUnit("1") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription("The percentage of broker memory used")) + .add( + "activemq.memory.limit", + metric -> + metric + .isUpDownCounter() + .hasUnit("By") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription("The amount of configured broker memory limit")) + .add( + "activemq.store.utilization", + metric -> + metric + .isGauge() + .hasUnit("1") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription("The percentage of broker persistent storage used")) + .add( + "activemq.store.limit", + metric -> + metric + .isUpDownCounter() + .hasUnit("By") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription("The amount of configured broker persistent storage limit")) + .add( + "activemq.temp.utilization", + metric -> + metric + .isGauge() + .hasUnit("1") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription("The percentage of broker non-persistent storage used")) + .add( + "activemq.temp.limit", + metric -> + metric + .isUpDownCounter() + .hasUnit("By") + .hasDataPointsWithAttributes(brokerAttributes) + .hasDescription( + "The amount of configured broker non-persistent storage limit")); + } +}