Skip to content

Jmx unit semconv alignment - Tomcat #13650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d081c29
add test code to test yaml jmx metrics
SylvainJuge Feb 25, 2025
e1f60cd
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-i…
SylvainJuge Mar 27, 2025
02e8a41
Merge branch 'main' into jmx-unit-semconv-alignment
robsunday Mar 28, 2025
dfae1ac
Tomcat metrics updated
robsunday Apr 1, 2025
e272d84
Tomcat test added
robsunday Apr 4, 2025
2d2c264
tomcat.active_session.count support added in test
robsunday Apr 4, 2025
15ad839
Comment added to test
robsunday Apr 4, 2025
297fb6b
Checkstyle error fix
robsunday Apr 4, 2025
e8fdc29
Attr name fixed
robsunday Apr 4, 2025
185e60d
Merge branch 'main' into jmx-unit-semconv-alignment
robsunday Apr 7, 2025
8a9bb0f
Test fixed.
robsunday Apr 7, 2025
1cc4080
Test fixed.
robsunday Apr 8, 2025
747ec32
Merge branch 'main' into jmx-unit-semconv-alignment
robsunday Apr 8, 2025
816b9ed
Merge branch 'main' into jmx-unit-semconv-alignment
robsunday Apr 22, 2025
910e837
Thread count related metrics fixed after code review
robsunday Apr 24, 2025
0dc6775
Merge branch 'main' into jmx-unit-semconv-alignment
robsunday Apr 24, 2025
1dcd568
Readme updates
robsunday Apr 24, 2025
38fb785
Merge branch 'main' into jmx-unit-semconv-alignment
robsunday May 14, 2025
99104ee
Update instrumentation/jmx-metrics/library/src/main/resources/jmx/rul…
robsunday May 14, 2025
f8cc647
Update instrumentation/jmx-metrics/library/src/main/resources/jmx/rul…
robsunday May 14, 2025
299b35c
Code review changes
robsunday May 14, 2025
ee7faec
Fixed tomcat.md
robsunday May 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion instrumentation/jmx-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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<String> FILES_TO_BE_TESTED =
Expand All @@ -32,7 +38,6 @@ class JmxMetricInsightInstallerTest {
"hadoop.yaml",
"jetty.yaml",
"kafka-broker.yaml",
"tomcat.yaml",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[for reviewer] Tomcat metrics definition is tested in a much better way in TomcatIntegrationTest.java

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By "much better way" here it means being tested against a real tomcat instance rather than trying to parse the yaml file to see if there are any errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moreover, it can now be easily tested with multiple tomcat versions

"wildfly.yaml"));

@Test
Expand Down
13 changes: 0 additions & 13 deletions instrumentation/jmx-metrics/javaagent/tomcat.md

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
# 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the processor name, I think we could maybe use tomcat.request.processor.name as it allows to reuse the tomcat.request namespace, and as I understand defining more namespaces than strictly needed in semconv and avoiding _ is the current strategy, even if we don't have anything extra to provide in the tomcat.request.processor name yet.

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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that processingTime and maxTime mbean attributes are related, so we should probably make sure their name reflect that, for example:

  • tomcat.request.max.time for maxTime + tomcat.request.total.time for processingTime
  • having the total or max not at the end of the metric name helps to avoid the "do not use total" rule in semconv
  • alternatively, we could argue that the "do not use total" rule in semconv refers only to the _total and not total suffix and then use tomcat.request.time.max and tomcat.request.time.total but this might be harder to make people agree on this.

Copy link
Contributor Author

@robsunday robsunday May 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mapped maxTime to tomcat.request.duration.max to make it similar to one of http semconv metrics
I'd keep this name, and i could then map processingTime to tomcat.request.duration.total
I think .total suffix is fine because semconv rule you mentioned is talking specifically about _total suffix, not .total

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth to ask and double-check in the SIG meeting if .total could be fine as suffix, because if it does then it sounds like a good strategy that would be consistent with other "pre-aggregated metrics", for example it's easy to add an tomcat.request.duration.max

metric: request.duration.total
type: counter
sourceUnit: ms
unit: s
desc: Total time of 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: session.active.count
type: updowncounter
unit: "{session}"
desc: The number of currently active sessions.
maxActiveSessions:
metric: session.active.limit
type: updowncounter
unit: "{session}"
desc: Maximum 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.
maxThreads:
metric: limit
desc: Maximum thread count of the thread pool.
currentThreadsBusy:
metric: busy.count
desc: Number of busy threads in the thread pool.

Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* 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<String> yamlFiles = Collections.singletonList("tomcat.yaml");

yamlFiles.forEach(this::validateYamlSyntax);

List<String> 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.session.active.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.duration.total",
metric ->
metric
.hasDescription("Total time of 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.session.active.count",
metric ->
metric
.hasDescription("The number of currently active sessions.")
.hasUnit("{session}")
.isUpDownCounter()
.hasDataPointsWithOneAttribute(attributeWithAnyValue("tomcat.context")))
.add(
"tomcat.session.active.limit",
metric ->
metric
.hasDescription("Maximum 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.limit",
metric ->
metric
.hasDescription("Maximum 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));
}
}
16 changes: 16 additions & 0 deletions instrumentation/jmx-metrics/library/tomcat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Tomcat Metrics

Here is the list of metrics based on MBeans exposed by Tomcat.

| Metric Name | Type | Attributes | Description |
|-------------------------------|---------------|-----------------------------------------------------|--------------------------------------------|
| tomcat.session.active.count | UpDownCounter | tomcat.context | The number of currently active sessions. |
| tomcat.session.active.limit | UpDownCounter | tomcat.context | Maximum 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.duration.total | Counter | tomcat.request_processor.name | Total time of 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.max | UpDownCounter | tomcat.thread_pool.name | Maximum thread count of the thread pool. |
| tomcat.thread.busy.count | UpDownCounter | tomcat.thread_pool.name | Number of busy threads in the thread pool. |
Loading