diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java index 761b6c842..57105ca90 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/JettyIntegrationTest.java @@ -56,7 +56,7 @@ void endToEnd() throws InterruptedException { metric, "jetty.session.count", "The number of sessions established in total.", - "{sessions}", + "{session}", attrs -> attrs.containsKey("resource")), metric -> assertSumWithAttributes( @@ -73,13 +73,13 @@ void endToEnd() throws InterruptedException { "s", attrs -> attrs.containsKey("resource")), metric -> - assertSum(metric, "jetty.select.count", "The number of select calls.", "{operations}"), + assertSum(metric, "jetty.select.count", "The number of select calls.", "{operation}"), metric -> assertGaugeWithAttributes( metric, "jetty.thread.count", "The current number of threads.", - "{threads}", + "{thread}", attrs -> attrs.contains(entry("state", "busy")), attrs -> attrs.contains(entry("state", "idle"))), metric -> @@ -87,6 +87,6 @@ void endToEnd() throws InterruptedException { metric, "jetty.thread.queue.count", "The current number of threads in the queue.", - "{threads}")); + "{thread}")); } } diff --git a/jmx-metrics/src/main/resources/target-systems/jetty.groovy b/jmx-metrics/src/main/resources/target-systems/jetty.groovy index f7f4a5869..c034d6976 100644 --- a/jmx-metrics/src/main/resources/target-systems/jetty.groovy +++ b/jmx-metrics/src/main/resources/target-systems/jetty.groovy @@ -15,10 +15,10 @@ */ def beanSelector = otel.mbean("org.eclipse.jetty.io:context=*,type=managedselector,id=*") -otel.instrument(beanSelector, "jetty.select.count", "The number of select calls.", "{operations}","selectCount", otel.&longCounterCallback) +otel.instrument(beanSelector, "jetty.select.count", "The number of select calls.", "{operation}","selectCount", otel.&longCounterCallback) def beanSessions = otel.mbean("org.eclipse.jetty.server.session:context=*,type=sessionhandler,id=*") -otel.instrument(beanSessions, "jetty.session.count", "The number of sessions established in total.", "{sessions}", +otel.instrument(beanSessions, "jetty.session.count", "The number of sessions established in total.", "{session}", ["resource" : { mbean -> mbean.name().getKeyProperty("context") }], "sessionsCreated", otel.&longCounterCallback) otel.instrument(beanSessions, "jetty.session.time.total", "The total time sessions have been active.", "s", @@ -29,9 +29,9 @@ otel.instrument(beanSessions, "jetty.session.time.max", "The maximum amount of t "sessionTimeMax", otel.&longValueCallback) def beanThreads = otel.mbean("org.eclipse.jetty.util.thread:type=queuedthreadpool,id=*") -otel.instrument(beanThreads, "jetty.thread.count", "The current number of threads.", "{threads}", +otel.instrument(beanThreads, "jetty.thread.count", "The current number of threads.", "{thread}", [ "busyThreads":["state" : {"busy"}], "idleThreads": ["state" : {"idle"}] ], otel.&longValueCallback) -otel.instrument(beanThreads, "jetty.thread.queue.count", "The current number of threads in the queue.", "{threads}","queueSize", otel.&longValueCallback) +otel.instrument(beanThreads, "jetty.thread.queue.count", "The current number of threads in the queue.", "{thread}","queueSize", otel.&longValueCallback) diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java index 036996f5e..675c5d39d 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java @@ -23,14 +23,7 @@ protected GenericContainer createTargetContainer(int jmxPort) { new ImageFromDockerfile() .withDockerfileFromBuilder( builder -> builder.from("apache/activemq-classic:5.18.6").build())) - .withEnv( - "JAVA_TOOL_OPTIONS", - "-Dcom.sun.management.jmxremote.port=" - + jmxPort - + " -Dcom.sun.management.jmxremote.rmi.port=" - + jmxPort - + " -Dcom.sun.management.jmxremote.ssl=false" - + " -Dcom.sun.management.jmxremote.authenticate=false") + .withEnv("JAVA_TOOL_OPTIONS", genericJmxJvmArguments(jmxPort)) .withStartupTimeout(Duration.ofMinutes(2)) .waitingFor(Wait.forListeningPort()); } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java index 756cbd502..3a6608ac2 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java @@ -18,24 +18,17 @@ import org.assertj.core.api.MapAssert; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; -import org.testcontainers.images.builder.ImageFromDockerfile; public class CassandraIntegrationTest extends TargetSystemIntegrationTest { @Override protected GenericContainer createTargetContainer(int jmxPort) { - return new GenericContainer<>( - new ImageFromDockerfile() - .withDockerfileFromBuilder(builder -> builder.from("cassandra:5.0.2").build())) + return new GenericContainer<>("cassandra:5.0.2") .withEnv( "JVM_EXTRA_OPTS", - " -Dcassandra.jmx.remote.port=" - + jmxPort - + " -Dcom.sun.management.jmxremote.rmi.port=" - + jmxPort - + " -Dcom.sun.management.jmxremote.local.only=false" - + " -Dcom.sun.management.jmxremote.ssl=false" - + " -Dcom.sun.management.jmxremote.authenticate=false") + genericJmxJvmArguments(jmxPort) + // making cassandra startup faster for single node, from ~1min to ~15s + + " -Dcassandra.skip_wait_for_gossip_to_settle=0 -Dcassandra.initial_token=0") .withStartupTimeout(Duration.ofMinutes(2)) .waitingFor(Wait.forLogMessage(".*Startup complete.*", 1)); } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java new file mode 100644 index 000000000..d6d8502b2 --- /dev/null +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java @@ -0,0 +1,100 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.jmxscraper.target_systems; + +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertGauge; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertGaugeWithAttributes; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertSumWithAttributes; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertSumWithAttributesMultiplePoints; + +import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; +import java.time.Duration; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.images.builder.ImageFromDockerfile; + +public class JettyIntegrationTest extends TargetSystemIntegrationTest { + + @Override + protected GenericContainer createTargetContainer(int jmxPort) { + GenericContainer container = + new GenericContainer<>( + new ImageFromDockerfile() + .withDockerfileFromBuilder( + builder -> + builder + .from("jetty:11") + .run( + "java", + "-jar", + "/usr/local/jetty/start.jar", + "--add-to-startd=jmx,stats,http") + .run("mkdir -p /var/lib/jetty/webapps/ROOT/") + .run("touch /var/lib/jetty/webapps/ROOT/index.html") + .build())); + + container + .withEnv("JAVA_OPTIONS", genericJmxJvmArguments(jmxPort)) + .withStartupTimeout(Duration.ofMinutes(2)) + .waitingFor(Wait.forLogMessage(".*Started Server.*", 1)); + + return container; + } + + @Override + protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scraper) { + return scraper.withTargetSystem("jetty"); + } + + @Override + protected void verifyMetrics() { + waitAndAssertMetrics( + metric -> + assertSumWithAttributes( + metric, + "jetty.session.count", + "The number of sessions established in total.", + "{session}", + attrs -> attrs.containsKey("resource")), + metric -> + assertSumWithAttributes( + metric, + "jetty.session.time.total", + "The total time sessions have been active.", + "s", + attrs -> attrs.containsKey("resource")), + metric -> + assertGaugeWithAttributes( + metric, + "jetty.session.time.max", + "The maximum amount of time a session has been active.", + "s", + attrs -> attrs.containsKey("resource")), + metric -> + assertSumWithAttributesMultiplePoints( + metric, + "jetty.select.count", + "The number of select calls.", + "{operation}", + /* isMonotonic= */ true, + // minor divergence from jetty.groovy with extra metrics attributes + attrs -> attrs.containsKey("context").containsKey("id")), + metric -> + assertGaugeWithAttributes( + metric, + "jetty.thread.count", + "The current number of threads.", + "{thread}", + attrs -> attrs.containsEntry("state", "busy"), + attrs -> attrs.containsEntry("state", "idle")), + metric -> + assertGauge( + metric, + "jetty.thread.queue.count", + "The current number of threads in the queue.", + "{thread}")); + } +} diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/MetricAssertions.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/MetricAssertions.java index cbd46c350..713d2f21b 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/MetricAssertions.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/MetricAssertions.java @@ -92,6 +92,22 @@ static void assertSumWithAttributes( assertAttributedPoints(metric.getSum().getDataPointsList(), attributeGroupAssertions); } + @SafeVarargs + static void assertSumWithAttributesMultiplePoints( + Metric metric, + String name, + String description, + String unit, + boolean isMonotonic, + Consumer>... attributeGroupAssertions) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasSum()).isTrue(); + assertThat(metric.getSum().getIsMonotonic()).isEqualTo(isMonotonic); + assertAttributedMultiplePoints(metric.getSum().getDataPointsList(), attributeGroupAssertions); + } + @SafeVarargs static void assertGaugeWithAttributes( Metric metric, @@ -127,6 +143,7 @@ private static void assertAttributedPoints( Arrays.stream(attributeGroupAssertions) .map(assertion -> (Consumer>) m -> assertion.accept(assertThat(m))) .toArray(Consumer[]::new); + assertThat(points) .extracting( numberDataPoint -> @@ -136,4 +153,22 @@ private static void assertAttributedPoints( KeyValue::getKey, keyValue -> keyValue.getValue().getStringValue()))) .satisfiesExactlyInAnyOrder(assertions); } + + @SuppressWarnings("unchecked") + private static void assertAttributedMultiplePoints( + List points, + Consumer>... attributeGroupAssertions) { + + points.stream() + .map(NumberDataPoint::getAttributesList) + .forEach( + kvList -> { + Map kvMap = + kvList.stream() + .collect( + Collectors.toMap(KeyValue::getKey, kv -> kv.getValue().getStringValue())); + Arrays.stream(attributeGroupAssertions) + .forEach(assertion -> assertion.accept(assertThat(kvMap))); + }); + } } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java index 510b4eab5..4d4ca988b 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java @@ -196,4 +196,14 @@ public void export( sb.http(0); } } + + protected static String genericJmxJvmArguments(int port) { + return "-Dcom.sun.management.jmxremote.local.only=false" + + " -Dcom.sun.management.jmxremote.authenticate=false" + + " -Dcom.sun.management.jmxremote.ssl=false" + + " -Dcom.sun.management.jmxremote.port=" + + port + + " -Dcom.sun.management.jmxremote.rmi.port=" + + port; + } } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java index 76bacf265..7b1313a1b 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java @@ -30,15 +30,7 @@ protected GenericContainer createTargetContainer(int jmxPort) { "https://tomcat.apache.org/tomcat-9.0-doc/appdev/sample/sample.war", "/usr/local/tomcat/webapps/ROOT.war") .build())) - .withEnv( - "CATALINA_OPTS", - "-Dcom.sun.management.jmxremote.local.only=false" - + " -Dcom.sun.management.jmxremote.authenticate=false" - + " -Dcom.sun.management.jmxremote.ssl=false" - + " -Dcom.sun.management.jmxremote.port=" - + jmxPort - + " -Dcom.sun.management.jmxremote.rmi.port=" - + jmxPort) + .withEnv("CATALINA_OPTS", genericJmxJvmArguments(jmxPort)) .withStartupTimeout(Duration.ofMinutes(2)) .waitingFor(Wait.forListeningPort()); } diff --git a/jmx-scraper/src/main/resources/jetty.yaml b/jmx-scraper/src/main/resources/jetty.yaml new file mode 100644 index 000000000..2511b5d7d --- /dev/null +++ b/jmx-scraper/src/main/resources/jetty.yaml @@ -0,0 +1,65 @@ +--- + +rules: + + - bean: org.eclipse.jetty.io:context=*,type=managedselector,id=* + mapping: + selectCount: + metric: jetty.select.count + type: counter + unit: "{operation}" + desc: The number of select calls. + metricAttribute: + # minor divergence from jetty.groovy with extra attribute(s) + # 'id' is a numerical value in [0,9] by default + # 'context' is a high cardinality value like 'HTTP_1_1@7674f035' but likely stable for the + # duration of the jetty process lifecycle + context: param(context) + id: param(id) + + - bean: org.eclipse.jetty.server.session:context=*,type=sessionhandler,id=* + prefix: jetty.session. + metricAttribute: + resource: param(context) + mapping: + sessionsCreated: + metric: count + type: counter + unit: "{session}" + desc: The number of sessions established in total. + sessionTimeTotal: + metric: time.total + type: counter + unit: s + desc: The total time sessions have been active. + sessionTimeMax: + metric: time.max + # here a 'counter' seems more appropriate but gauge reflects jetty.groovy impl. + type: gauge + unit: s + desc: The maximum amount of time a session has been active. + + - bean: org.eclipse.jetty.util.thread:type=queuedthreadpool,id=* + # here the 'id' can be ignored as it's usually a single value equal to '0' + prefix: jetty.thread. + unit: "{thread}" + mapping: + busyThreads: + metric: count + # here an 'updowncounter' seems more appropriate but gauge reflects jetty.groovy impl. + type: gauge + desc: The current number of threads. + metricAttribute: + state: const(busy) + idleThreads: + metric: count + # here an 'updowncounter' seems more appropriate but gauge reflects jetty.groovy impl. + type: gauge + desc: The current number of threads. + metricAttribute: + state: const(idle) + queueSize: + metric: queue.count + # here an 'updowncounter' seems more appropriate but gauge reflects jetty.groovy impl. + type: gauge + desc: The current number of threads in the queue.