diff --git a/instrumentation/jmx-metrics/README.md b/instrumentation/jmx-metrics/README.md index eea8b7a610f5..d4164f251a4b 100644 --- a/instrumentation/jmx-metrics/README.md +++ b/instrumentation/jmx-metrics/README.md @@ -29,7 +29,7 @@ No targets are enabled by default. The supported target environments are listed - [camel](javaagent/camel.md) - [jetty](javaagent/jetty.md) - [kafka-broker](javaagent/kafka-broker.md) -- [tomcat](javaagent/tomcat.md) +- [tomcat](library/tomcat.md) - [wildfly](javaagent/wildfly.md) - [hadoop](javaagent/hadoop.md) diff --git a/instrumentation/jmx-metrics/javaagent/src/main/resources/jmx/rules/tomcat.yaml b/instrumentation/jmx-metrics/javaagent/src/main/resources/jmx/rules/tomcat.yaml deleted file mode 100644 index 3f03d8f8ae56..000000000000 --- a/instrumentation/jmx-metrics/javaagent/src/main/resources/jmx/rules/tomcat.yaml +++ /dev/null @@ -1,79 +0,0 @@ ---- -# For Tomcat, the default JMX domain is "Catalina:", however with some deployments like embedded in spring-boot -# we can have the "Tomcat:" domain used, thus we use both MBean names for the metrics. - -rules: - - beans: - - Catalina:type=GlobalRequestProcessor,name=* - - Tomcat:type=GlobalRequestProcessor,name=* - unit: "1" - prefix: http.server.tomcat. - metricAttribute: - name: param(name) - mapping: - errorCount: - metric: errorCount - type: gauge - desc: The number of errors per second on all request processors - requestCount: - metric: requestCount - type: gauge - desc: The number of requests per second across all request processors - maxTime: - metric: maxTime - type: gauge - unit: ms - desc: The longest request processing time - processingTime: - metric: processingTime - type: counter - unit: ms - desc: Total time for processing all requests - bytesReceived: - metric: traffic - type: counter - unit: By - desc: The number of bytes transmitted - metricAttribute: - direction: const(received) - bytesSent: - metric: traffic - type: counter - unit: By - desc: The number of bytes transmitted - metricAttribute: - direction: const(sent) - - - beans: - - Catalina:type=Manager,host=localhost,context=* - - Tomcat:type=Manager,host=localhost,context=* - unit: "1" - prefix: http.server.tomcat. - type: updowncounter - metricAttribute: - context: param(context) - mapping: - activeSessions: - metric: sessions.activeSessions - desc: The number of active sessions - - - beans: - - Catalina:type=ThreadPool,name=* - - Tomcat:type=ThreadPool,name=* - unit: "{threads}" - prefix: http.server.tomcat. - type: updowncounter - metricAttribute: - name: param(name) - mapping: - currentThreadCount: - metric: threads - desc: Thread Count of the Thread Pool - metricAttribute: - state: const(idle) - currentThreadsBusy: - metric: threads - desc: Thread Count of the Thread Pool - metricAttribute: - state: const(busy) - 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 55d0c51f8c1b..88d8d6787357 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 @@ -22,6 +22,12 @@ import java.util.Set; import org.junit.jupiter.api.Test; +/** + * TODO: This test will eventually go away when all yaml files are moved from javaagent to library + * directory. When yaml file is moved from javaagent to library then appropriate item must be + * removed from JmxMetricInsightInstallerTest#FILES_TO_BE_TESTED and corresponding test must be + * added in the library. + */ class JmxMetricInsightInstallerTest { private static final String PATH_TO_ALL_EXISTING_RULES = "src/main/resources/jmx/rules"; private static final Set FILES_TO_BE_TESTED = @@ -32,7 +38,6 @@ class JmxMetricInsightInstallerTest { "hadoop.yaml", "jetty.yaml", "kafka-broker.yaml", - "tomcat.yaml", "wildfly.yaml")); @Test diff --git a/instrumentation/jmx-metrics/javaagent/tomcat.md b/instrumentation/jmx-metrics/javaagent/tomcat.md deleted file mode 100644 index 6ac2e3fea16e..000000000000 --- a/instrumentation/jmx-metrics/javaagent/tomcat.md +++ /dev/null @@ -1,13 +0,0 @@ -# Tomcat Metrics - -Here is the list of metrics based on MBeans exposed by Tomcat. - -| Metric Name | Type | Attributes | Description | -| ------------------------------------------ | ------------- | --------------- | --------------------------------------------------------------- | -| http.server.tomcat.sessions.activeSessions | UpDownCounter | context | The number of active sessions | -| http.server.tomcat.errorCount | Gauge | name | The number of errors per second on all request processors | -| http.server.tomcat.requestCount | Gauge | name | The number of requests per second across all request processors | -| http.server.tomcat.maxTime | Gauge | name | The longest request processing time | -| http.server.tomcat.processingTime | Counter | name | Represents the total time for processing all requests | -| http.server.tomcat.traffic | Counter | name, direction | The number of bytes transmitted | -| http.server.tomcat.threads | UpDownCounter | name, state | Thread Count of the Thread Pool | diff --git a/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml b/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml new file mode 100644 index 000000000000..db1773df68c9 --- /dev/null +++ b/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml @@ -0,0 +1,78 @@ +--- +# For Tomcat, the default JMX domain is "Catalina:", however with some deployments like embedded in spring-boot +# we can have the "Tomcat:" domain used, thus we use both MBean names for the metrics. + +rules: + - beans: + - Catalina:type=GlobalRequestProcessor,name=* + - Tomcat:type=GlobalRequestProcessor,name=* + prefix: tomcat. + metricAttribute: + tomcat.request_processor.name: param(name) + mapping: + errorCount: + metric: error.count + type: counter + unit: "{error}" + desc: The number of errors. + requestCount: + metric: request.count + type: counter + unit: "{request}" + desc: The number of requests processed. + maxTime: + metric: request.duration.max + type: gauge + sourceUnit: ms + unit: s + desc: The longest request processing time. + processingTime: + metric: request.processing_time + type: counter + sourceUnit: ms + unit: s + desc: Total time for processing all requests. + bytesReceived: + metric: &metric network.io + type: &type counter + unit: &unit By + desc: &desc The number of bytes transmitted. + metricAttribute: + network.io.direction: const(receive) + bytesSent: + metric: *metric + type: *type + unit: *unit + desc: *desc + metricAttribute: + network.io.direction: const(transmit) + + - beans: + - Catalina:type=Manager,host=localhost,context=* + - Tomcat:type=Manager,host=localhost,context=* + prefix: tomcat. + metricAttribute: + tomcat.context: param(context) + mapping: + activeSessions: + metric: active_session.count + type: updowncounter + unit: "{session}" + desc: The number of active sessions. + + - beans: + - Catalina:type=ThreadPool,name=* + - Tomcat:type=ThreadPool,name=* + unit: "{thread}" + prefix: tomcat.thread. + type: updowncounter + metricAttribute: + tomcat.thread_pool.name: param(name) + mapping: + currentThreadCount: + metric: count + desc: Total thread count of the thread pool. + currentThreadsBusy: + metric: busy.count + desc: Number of busy threads in the thread pool. + diff --git a/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/rules/TomcatIntegrationTest.java b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/rules/TomcatIntegrationTest.java new file mode 100644 index 000000000000..456834dcbeee --- /dev/null +++ b/instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/rules/TomcatIntegrationTest.java @@ -0,0 +1,138 @@ +/* + * 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 static io.opentelemetry.instrumentation.jmx.rules.assertions.DataPointAttributes.attributeWithAnyValue; + +import io.opentelemetry.instrumentation.jmx.rules.assertions.AttributeMatcher; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +public class TomcatIntegrationTest extends TargetSystemTest { + + @ParameterizedTest + @CsvSource({ + "tomcat:10.0, https://tomcat.apache.org/tomcat-10.0-doc/appdev/sample/sample.war", + "tomcat:9.0, https://tomcat.apache.org/tomcat-9.0-doc/appdev/sample/sample.war" + }) + void testCollectedMetrics(String dockerImageName, String sampleWebApplicationUrl) + throws Exception { + List yamlFiles = Collections.singletonList("tomcat.yaml"); + + yamlFiles.forEach(this::validateYamlSyntax); + + List jvmArgs = new ArrayList<>(); + jvmArgs.add(javaAgentJvmArgument()); + jvmArgs.addAll(javaPropertiesToJvmArgs(otelConfigProperties(yamlFiles))); + + // testing with a basic tomcat image as test application to capture JVM metrics + GenericContainer target = + new GenericContainer<>(dockerImageName) + .withEnv("CATALINA_OPTS", String.join(" ", jvmArgs)) + .withStartupTimeout(Duration.ofMinutes(2)) + .withExposedPorts(8080) + .waitingFor(Wait.forListeningPorts(8080)); + + copyFilesToTarget(target, yamlFiles); + + startTarget(target); + + // Deploy example web application to the tomcat to enable reporting tomcat.active_session.count + // metric + target.execInContainer("rm", "-fr", "/usr/local/tomcat/webapps/ROOT"); + target.execInContainer( + "curl", sampleWebApplicationUrl, "-o", "/usr/local/tomcat/webapps/ROOT.war"); + + verifyMetrics(createMetricsVerifier()); + } + + private static MetricsVerifier createMetricsVerifier() { + AttributeMatcher requestProcessorNameAttribute = + attribute("tomcat.request_processor.name", "\"http-nio-8080\""); + AttributeMatcher threadPoolNameAttribute = + attribute("tomcat.thread_pool.name", "\"http-nio-8080\""); + + return MetricsVerifier.create() + .add( + "tomcat.error.count", + metric -> + metric + .hasDescription("The number of errors.") + .hasUnit("{error}") + .isCounter() + .hasDataPointsWithOneAttribute(requestProcessorNameAttribute)) + .add( + "tomcat.request.count", + metric -> + metric + .hasDescription("The number of requests processed.") + .hasUnit("{request}") + .isCounter() + .hasDataPointsWithOneAttribute(requestProcessorNameAttribute)) + .add( + "tomcat.request.duration.max", + metric -> + metric + .hasDescription("The longest request processing time.") + .hasUnit("s") + .isGauge() + .hasDataPointsWithOneAttribute(requestProcessorNameAttribute)) + .add( + "tomcat.request.processing_time", + metric -> + metric + .hasDescription("Total time for processing all requests.") + .hasUnit("s") + .isCounter() + .hasDataPointsWithOneAttribute(requestProcessorNameAttribute)) + .add( + "tomcat.network.io", + metric -> + metric + .hasDescription("The number of bytes transmitted.") + .hasUnit("By") + .isCounter() + .hasDataPointsWithAttributes( + attributeGroup( + attribute("network.io.direction", "receive"), + requestProcessorNameAttribute), + attributeGroup( + attribute("network.io.direction", "transmit"), + requestProcessorNameAttribute))) + .add( + "tomcat.active_session.count", + metric -> + metric + .hasDescription("The number of active sessions.") + .hasUnit("{session}") + .isUpDownCounter() + .hasDataPointsWithOneAttribute(attributeWithAnyValue("tomcat.context"))) + .add( + "tomcat.thread.count", + metric -> + metric + .hasDescription("Total thread count of the thread pool.") + .hasUnit("{thread}") + .isUpDownCounter() + .hasDataPointsWithOneAttribute(threadPoolNameAttribute)) + .add( + "tomcat.thread.busy.count", + metric -> + metric + .hasDescription("Number of busy threads in the thread pool.") + .hasUnit("{thread}") + .isUpDownCounter() + .hasDataPointsWithOneAttribute(threadPoolNameAttribute)); + } +} diff --git a/instrumentation/jmx-metrics/library/tomcat.md b/instrumentation/jmx-metrics/library/tomcat.md new file mode 100644 index 000000000000..c1a3778a2ca3 --- /dev/null +++ b/instrumentation/jmx-metrics/library/tomcat.md @@ -0,0 +1,14 @@ +# Tomcat Metrics + +Here is the list of metrics based on MBeans exposed by Tomcat. + +| Metric Name | Type | Attributes | Description | +|--------------------------------|---------------|-----------------------------------------------------|---------------------------------------------| +| tomcat.active_session.count | UpDownCounter | tomcat.context | The number of active sessions. | +| tomcat.error.count | Counter | tomcat.request_processor.name | The number of errors. | +| tomcat.request.count | Counter | tomcat.request_processor.name | The number of requests processed. | +| tomcat.request.duration.max | Gauge | tomcat.request_processor.name | The longest request processing time. | +| tomcat.request.processing_time | Counter | tomcat.request_processor.name | Total time for processing all requests. | +| tomcat.network.io | Counter | tomcat.request_processor.name, network.io.direction | The number of bytes transmitted. | +| tomcat.thread.count | UpDownCounter | tomcat.thread_pool.name | Total thread count of the thread pool. | +| tomcat.thread.busy.count | UpDownCounter | tomcat.thread_pool.name | Number of busy threads in the thread pool. |