From 49cfb99d7e4a59043cbd92c4cd983b8013fa0e52 Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Mon, 30 Sep 2024 15:46:56 +0200 Subject: [PATCH 01/21] Additional logger call --- .../io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java index dd509aa13..eebca5fd4 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java @@ -138,6 +138,7 @@ private Map<String, Object> buildEnv() { @SuppressWarnings("BanJNDI") private static JMXConnector doConnect(JMXServiceURL url, Map<String, Object> env) throws IOException { + logger.info("Connecting to " + url); return JMXConnectorFactory.connect(url, env); } From a8f8ace496d5166c9dea333da438d0485be4e853 Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Mon, 30 Sep 2024 16:40:55 +0200 Subject: [PATCH 02/21] Test exception thrown --- .../contrib/jmxscraper/JmxConnectorBuilderTest.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index 132605242..4fd1fb6bf 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -34,7 +34,10 @@ void noAuth() { try (TestAppContainer app = new TestAppContainer().withNetwork(network).withJmxPort(9990)) { app.start(); testConnector( - () -> JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(9990)).build()); + () -> { + throw new RuntimeException("TEST"); + } + ); } } From e1043d11f495db589fb91268533c1790ae237eae Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Mon, 30 Sep 2024 16:41:55 +0200 Subject: [PATCH 03/21] Spotless fix --- .../contrib/jmxscraper/JmxConnectorBuilderTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index 4fd1fb6bf..c5ccd6639 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -36,8 +36,7 @@ void noAuth() { testConnector( () -> { throw new RuntimeException("TEST"); - } - ); + }); } } From 5948f5751da817756e9d219d4ec4eb19cd8f75f2 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:38:01 +0200 Subject: [PATCH 04/21] add snapshot dependency --- jmx-scraper/build.gradle.kts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index bab7b7cb7..d2bea917c 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -13,9 +13,20 @@ otelJava.moduleName.set("io.opentelemetry.contrib.jmxscraper") application.mainClass.set("io.opentelemetry.contrib.jmxscraper.JmxScraper") +repositories { + mavenCentral() + mavenLocal() + // TODO: remove snapshot repository once 2.9.0 is released + maven { + setUrl("https://oss.sonatype.org/content/repositories/snapshots") + } + +} + + dependencies { // TODO remove snapshot dependency on upstream once 2.9.0 is released - // api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.9.0-SNAPSHOT-alpha",)) + api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.9.0-alpha-SNAPSHOT")) implementation("io.opentelemetry:opentelemetry-api") implementation("io.opentelemetry:opentelemetry-sdk") From 7f7681b91ee25fa8eedb1565bdcdca58a17d0bf6 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:06:44 +0200 Subject: [PATCH 05/21] first working poc with tomcat --- jmx-scraper/build.gradle.kts | 3 + .../contrib/jmxscraper/JmxScraper.java | 95 ++++++++++++++----- jmx-scraper/src/main/resources/tomcat.yaml | 84 ++++++++++++++++ 3 files changed, 160 insertions(+), 22 deletions(-) create mode 100644 jmx-scraper/src/main/resources/tomcat.yaml diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index d2bea917c..569582bf9 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -33,6 +33,9 @@ dependencies { implementation("io.opentelemetry:opentelemetry-sdk-metrics") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + runtimeOnly("io.opentelemetry:opentelemetry-exporter-otlp") + runtimeOnly("io.opentelemetry:opentelemetry-exporter-logging") + implementation("io.opentelemetry.instrumentation:opentelemetry-jmx-metrics") testImplementation("org.junit-pioneer:junit-pioneer") diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index ebed7c780..617b4702f 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -5,16 +5,22 @@ package io.opentelemetry.contrib.jmxscraper; +import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.contrib.jmxscraper.config.ConfigurationException; import io.opentelemetry.contrib.jmxscraper.config.JmxScraperConfig; +import io.opentelemetry.instrumentation.jmx.engine.JmxMetricInsight; +import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration; +import io.opentelemetry.instrumentation.jmx.yaml.RuleParser; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; @@ -23,10 +29,13 @@ public class JmxScraper { private static final Logger logger = Logger.getLogger(JmxScraper.class.getName()); private static final String CONFIG_ARG = "-config"; + private static final String OTEL_AUTOCONFIGURE = "otel.java.global-autoconfigure.enabled"; + private final JmxConnectorBuilder client; + private final JmxMetricInsight service; + private final JmxScraperConfig config; - // TODO depend on instrumentation 2.9.0 snapshot - // private final JmxMetricInsight service; + private final AtomicBoolean running = new AtomicBoolean(false); /** * Main method to create and run a {@link JmxScraper} instance. @@ -35,15 +44,23 @@ public class JmxScraper { */ @SuppressWarnings({"SystemOut", "SystemExitOutsideMain"}) public static void main(String[] args) { + + // enable SDK auto-configure if not explicitly set by user + if (System.getProperty(OTEL_AUTOCONFIGURE) == null) { + System.setProperty(OTEL_AUTOCONFIGURE, "true"); + } + try { JmxScraperConfig config = JmxScraperConfig.fromProperties(parseArgs(Arrays.asList(args)), System.getProperties()); // propagate effective user-provided configuration to JVM system properties + // this also enables SDK auto-configuration to use those properties config.propagateSystemProperties(); - // TODO: depend on instrumentation 2.9.0 snapshot - // service = JmxMetricInsight.createService(GlobalOpenTelemetry.get(), - // config.getIntervalMilliseconds()); - JmxScraper jmxScraper = new JmxScraper(JmxConnectorBuilder.createNew(config.getServiceUrl())); + + JmxMetricInsight service = JmxMetricInsight.createService(GlobalOpenTelemetry.get(), + config.getIntervalMilliseconds()); + JmxScraper jmxScraper = new JmxScraper(JmxConnectorBuilder.createNew(config.getServiceUrl()), + service, config); jmxScraper.start(); } catch (ArgumentsParsingException e) { @@ -109,29 +126,63 @@ private static Properties loadPropertiesFromPath(String path) throws Configurati } } - JmxScraper(JmxConnectorBuilder client) { + JmxScraper(JmxConnectorBuilder client, JmxMetricInsight service, JmxScraperConfig config) { this.client = client; + this.service = service; + this.config = config; } private void start() throws IOException { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + logger.info("JMX scraping stopped"); + running.set(false); + })); + + try (JMXConnector connector = client.build()) { + MBeanServerConnection connection = connector.getMBeanServerConnection(); + service.startRemote(getMetricConfig(config), () -> Collections.singletonList(connection)); + + running.set(true); + logger.info("JMX scraping started"); + + while (running.get()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + // silenty ignored + } + } + } + } - JMXConnector connector = client.build(); - - @SuppressWarnings("unused") - MBeanServerConnection connection = connector.getMBeanServerConnection(); - - // TODO: depend on instrumentation 2.9.0 snapshot - // MetricConfiguration metricConfig = new MetricConfiguration(); - // TODO create JMX insight config from scraper config - // service.startRemote(metricConfig, () -> Collections.singletonList(connection)); + private static MetricConfiguration getMetricConfig(JmxScraperConfig scraperConfig) { + MetricConfiguration config = new MetricConfiguration(); + for (String system : scraperConfig.getTargetSystems()) { + try { + addRulesForSystem(system, config); + } catch (RuntimeException e) { + logger.warning("unable to load rules for system " + system + ": " + e.getMessage()); + } + } + // TODO : add ability for user to provide custom yaml configurations - logger.info("JMX scraping started"); + return config; + } - // TODO: wait a bit to keep the JVM running, this won't be needed once calling jmx insight - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - throw new IllegalStateException(e); + private static void addRulesForSystem(String system, MetricConfiguration conf) { + String yamlResource = system + ".yaml"; + try (InputStream inputStream = + JmxScraper.class.getClassLoader().getResourceAsStream(yamlResource)) { + if (inputStream != null) { + RuleParser parserInstance = RuleParser.get(); + parserInstance.addMetricDefsTo(conf, inputStream, system); + } else { + throw new IllegalStateException("no support for " + system); + } + } catch (Exception e) { + throw new IllegalStateException("error while loading rules for system " + system, e); } } + + } diff --git a/jmx-scraper/src/main/resources/tomcat.yaml b/jmx-scraper/src/main/resources/tomcat.yaml new file mode 100644 index 000000000..71adf88ff --- /dev/null +++ b/jmx-scraper/src/main/resources/tomcat.yaml @@ -0,0 +1,84 @@ +--- + +### TODO: for now this is a copy of tomcat.yaml from instrumentation +### This is only for testing and does not fit the current metrics definitions of JMX Gatherer +### + +# 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) + From 93bf770737629b94c5a593f4b5a0ec581aa87da4 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 2 Oct 2024 15:34:37 +0200 Subject: [PATCH 06/21] add comments for future config enhancement --- jmx-scraper/build.gradle.kts | 2 -- .../contrib/jmxscraper/JmxScraper.java | 23 +++++++++++-------- .../jmxscraper/config/JmxScraperConfig.java | 6 ++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 569582bf9..8a71277a3 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -20,10 +20,8 @@ repositories { maven { setUrl("https://oss.sonatype.org/content/repositories/snapshots") } - } - dependencies { // TODO remove snapshot dependency on upstream once 2.9.0 is released api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.9.0-alpha-SNAPSHOT")) diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index 617b4702f..e141d6388 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -46,6 +46,7 @@ public class JmxScraper { public static void main(String[] args) { // enable SDK auto-configure if not explicitly set by user + // TODO: refactor this to use AutoConfiguredOpenTelemetrySdk if (System.getProperty(OTEL_AUTOCONFIGURE) == null) { System.setProperty(OTEL_AUTOCONFIGURE, "true"); } @@ -57,10 +58,11 @@ public static void main(String[] args) { // this also enables SDK auto-configuration to use those properties config.propagateSystemProperties(); - JmxMetricInsight service = JmxMetricInsight.createService(GlobalOpenTelemetry.get(), - config.getIntervalMilliseconds()); - JmxScraper jmxScraper = new JmxScraper(JmxConnectorBuilder.createNew(config.getServiceUrl()), - service, config); + JmxMetricInsight service = + JmxMetricInsight.createService( + GlobalOpenTelemetry.get(), config.getIntervalMilliseconds()); + JmxScraper jmxScraper = + new JmxScraper(JmxConnectorBuilder.createNew(config.getServiceUrl()), service, config); jmxScraper.start(); } catch (ArgumentsParsingException e) { @@ -133,10 +135,13 @@ private static Properties loadPropertiesFromPath(String path) throws Configurati } private void start() throws IOException { - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - logger.info("JMX scraping stopped"); - running.set(false); - })); + Runtime.getRuntime() + .addShutdownHook( + new Thread( + () -> { + logger.info("JMX scraping stopped"); + running.set(false); + })); try (JMXConnector connector = client.build()) { MBeanServerConnection connection = connector.getMBeanServerConnection(); @@ -183,6 +188,4 @@ private static void addRulesForSystem(String system, MetricConfiguration conf) { throw new IllegalStateException("error while loading rules for system " + system, e); } } - - } diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java index edb7599fd..4e04fe145 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java @@ -55,9 +55,9 @@ public class JmxScraperConfig { private String serviceUrl = ""; private String customJmxScrapingConfigPath = ""; private Set<String> targetSystems = Collections.emptySet(); - private int intervalMilliseconds; - private String metricsExporterType = ""; - private String otlpExporterEndpoint = ""; + private int intervalMilliseconds; // TODO only used to set 'otel.metric.export.interval' from SDK + private String metricsExporterType = ""; // TODO only used to default to 'logging' if not set + private String otlpExporterEndpoint = ""; // TODO not really needed here as handled by SDK private String username = ""; private String password = ""; private String realm = ""; From ac67d0f14b0efb9d1f671b8a4e6d3fc7c9fe4f52 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:26:03 +0200 Subject: [PATCH 07/21] fix typo --- .../java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index e141d6388..853432493 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -154,7 +154,7 @@ private void start() throws IOException { try { Thread.sleep(100); } catch (InterruptedException e) { - // silenty ignored + // silently ignored } } } From 97038df08840747729904eff7af683cc9a743326 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 2 Oct 2024 17:26:12 +0200 Subject: [PATCH 08/21] add tomcat yaml config --- jmx-scraper/src/main/resources/tomcat.yaml | 87 +++++++++++----------- 1 file changed, 42 insertions(+), 45 deletions(-) diff --git a/jmx-scraper/src/main/resources/tomcat.yaml b/jmx-scraper/src/main/resources/tomcat.yaml index 71adf88ff..0c60bf49d 100644 --- a/jmx-scraper/src/main/resources/tomcat.yaml +++ b/jmx-scraper/src/main/resources/tomcat.yaml @@ -1,84 +1,81 @@ --- -### TODO: for now this is a copy of tomcat.yaml from instrumentation -### This is only for testing and does not fit the current metrics definitions of JMX Gatherer -### - # 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=Manager,host=localhost,context=* + - Tomcat:type=Manager,host=localhost,context=* + metricAttribute: + # minor divergence from tomcat.groovy to capture metric for all deployed webapps + context: param(context) + mapping: + activeSessions: + metric: sessions + type: gauge + unit: sessions + desc: The number of active sessions + - beans: - Catalina:type=GlobalRequestProcessor,name=* - Tomcat:type=GlobalRequestProcessor,name=* - unit: "1" - prefix: http.server.tomcat. + prefix: tomcat. metricAttribute: - name: param(name) + proto_handler: param(name) mapping: errorCount: - metric: errorCount - type: gauge - desc: The number of errors per second on all request processors + metric: errors + type: counter + unit: errors + desc: The number of errors encountered requestCount: - metric: requestCount - type: gauge - desc: The number of requests per second across all request processors + metric: request_count + type: counter + unit: requests + desc: The total requests maxTime: - metric: maxTime + metric: max_time type: gauge unit: ms - desc: The longest request processing time + desc: The total requests processingTime: - metric: processingTime - type: counter + metric: processing_time + type: gauge unit: ms - desc: Total time for processing all requests - bytesReceived: + desc: The total processing time + bytesSent: metric: traffic type: counter - unit: By + unit: by desc: The number of bytes transmitted metricAttribute: - direction: const(received) - bytesSent: + direction: const(sent) + bytesReceived: metric: traffic type: counter - unit: By - desc: The number of bytes transmitted + unit: by + desc: The number of bytes received 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 + direction: const(received) - beans: - Catalina:type=ThreadPool,name=* - Tomcat:type=ThreadPool,name=* - unit: "{threads}" - prefix: http.server.tomcat. - type: updowncounter + prefix: tomcat. metricAttribute: - name: param(name) + proto_handler: param(name) mapping: currentThreadCount: metric: threads - desc: Thread Count of the Thread Pool + type: updowncounter + unit: threads metricAttribute: state: const(idle) currentThreadsBusy: metric: threads - desc: Thread Count of the Thread Pool + type: updowncounter + unit: threads metricAttribute: state: const(busy) - From 67a50a0c31f9fd0e82ce1803b8328b7c46e6011d Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:31:46 +0200 Subject: [PATCH 09/21] fix test logging + fail on unsupported system --- .../target_systems/TargetSystemIntegrationTest.java | 3 ++- .../opentelemetry/contrib/jmxscraper/JmxScraper.java | 10 ++++------ 2 files changed, 6 insertions(+), 7 deletions(-) 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 0552aa3bd..8ab2241f8 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 @@ -93,7 +93,7 @@ void endToEndTest() { target = createTargetContainer(JMX_PORT) - .withLogConsumer(new Slf4jLogConsumer(logger)) + .withLogConsumer(new Slf4jLogConsumer(logger).withPrefix("target-system")) .withNetwork(network) .withExposedPorts(JMX_PORT) .withNetworkAliases(TARGET_SYSTEM_NETWORK_ALIAS); @@ -114,6 +114,7 @@ void endToEndTest() { scraper = new JmxScraperContainer(otlpEndpoint) + .withLogConsumer(new Slf4jLogConsumer(logger).withPrefix("jmx-scraper")) .withNetwork(network) .withService(TARGET_SYSTEM_NETWORK_ALIAS, JMX_PORT); diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index 853432493..b1faa0854 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -77,6 +77,9 @@ public static void main(String[] args) { } catch (IOException e) { System.err.println("Unable to connect " + e.getMessage()); System.exit(2); + } catch (RuntimeException e) { + System.err.println("ERROR: " + e.getMessage()); + System.exit(3); } } @@ -163,14 +166,9 @@ private void start() throws IOException { private static MetricConfiguration getMetricConfig(JmxScraperConfig scraperConfig) { MetricConfiguration config = new MetricConfiguration(); for (String system : scraperConfig.getTargetSystems()) { - try { addRulesForSystem(system, config); - } catch (RuntimeException e) { - logger.warning("unable to load rules for system " + system + ": " + e.getMessage()); - } } // TODO : add ability for user to provide custom yaml configurations - return config; } @@ -182,7 +180,7 @@ private static void addRulesForSystem(String system, MetricConfiguration conf) { RuleParser parserInstance = RuleParser.get(); parserInstance.addMetricDefsTo(conf, inputStream, system); } else { - throw new IllegalStateException("no support for " + system); + throw new IllegalStateException("no support for system" + system); } } catch (Exception e) { throw new IllegalStateException("error while loading rules for system " + system, e); From 26efa72a88b975019f36f944a2856bd570cb993b Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Thu, 3 Oct 2024 17:07:02 +0200 Subject: [PATCH 10/21] Integration tests are now working on macOS --- .../jmxscraper/JmxConnectorBuilderTest.java | 16 ++++--- .../contrib/jmxscraper/PortSelector.java | 42 +++++++++++++++++++ .../contrib/jmxscraper/TestAppContainer.java | 26 ++++++++---- .../TargetSystemIntegrationTest.java | 33 ++++----------- 4 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index c5ccd6639..f6589cc10 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -31,25 +31,29 @@ static void afterAll() { @Test void noAuth() { - try (TestAppContainer app = new TestAppContainer().withNetwork(network).withJmxPort(9990)) { + int port = PortSelector.getAvailableRandomPort(); + try (TestAppContainer app = + new TestAppContainer().withJmxPort(port).withJmxAccessibleFromHost()) { app.start(); testConnector( - () -> { - throw new RuntimeException("TEST"); - }); + () -> JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port)).build()); } } @Test void loginPwdAuth() { + int port = PortSelector.getAvailableRandomPort(); String login = "user"; String pwd = "t0p!Secret"; try (TestAppContainer app = - new TestAppContainer().withNetwork(network).withJmxPort(9999).withUserAuth(login, pwd)) { + new TestAppContainer() + .withJmxPort(port) + .withJmxAccessibleFromHost() + .withUserAuth(login, pwd)) { app.start(); testConnector( () -> - JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(9999)) + JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port)) .userCredentials(login, pwd) .build()); } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java new file mode 100644 index 000000000..400b7bb5e --- /dev/null +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.jmxscraper; + +import java.io.IOException; +import java.net.Socket; +import java.util.Random; + +/** Class used for finding random free network port from range 1024-65535 */ +public class PortSelector { + private static final Random random = new Random(System.currentTimeMillis()); + + private static final int MIN_PORT = 1024; + private static final int MAX_PORT = 65535; + + private PortSelector() {} + + /** + * @return random available TCP port + */ + public static synchronized int getAvailableRandomPort() { + int port; + + do { + port = random.nextInt(MAX_PORT - MIN_PORT + 1) + MIN_PORT; + } while (!isPortAvailable(port)); + + return port; + } + + private static boolean isPortAvailable(int port) { + // see https://stackoverflow.com/a/13826145 for the chosen implementation + try (Socket s = new Socket("localhost", port)) { + return false; + } catch (IOException e) { + return true; + } + } +} diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java index a38dd7ace..663a73bd6 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java @@ -25,7 +25,6 @@ public class TestAppContainer extends GenericContainer<TestAppContainer> { private final Map<String, String> properties; - private int port; private String login; private String pwd; @@ -46,9 +45,16 @@ public TestAppContainer() { @CanIgnoreReturnValue public TestAppContainer withJmxPort(int port) { - this.port = port; properties.put("com.sun.management.jmxremote.port", Integer.toString(port)); - return this.withExposedPorts(port); + properties.put("com.sun.management.jmxremote.rmi.port", Integer.toString(port)); + + // To get host->container JMX connection working docker must expose JMX/RMI port under the same + // port number. Because of this testcontainers' standard exposed port randomization approach + // can't be used. + // Explanation: + // https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/6 + addFixedExposedPort(port, port); + return this; } @CanIgnoreReturnValue @@ -58,9 +64,17 @@ public TestAppContainer withUserAuth(String login, String pwd) { return this; } + @CanIgnoreReturnValue + public TestAppContainer withJmxAccessibleFromHost() { + properties.put("java.rmi.server.hostname", getHost()); + return this; + } + @Override public void start() { - + // properties.put("com.sun.management.jmxremote.local.only", "false"); + // properties.put("java.rmi.server.logCalls", "true"); + // // TODO: add support for ssl properties.put("com.sun.management.jmxremote.ssl", "false"); @@ -92,11 +106,9 @@ public void start() { this.withEnv("JAVA_TOOL_OPTIONS", confArgs); - logger().info("Test application JAVA_TOOL_OPTIONS = " + confArgs); + logger().info("Test application JAVA_TOOL_OPTIONS = {}", confArgs); super.start(); - - logger().info("Test application JMX port mapped to {}:{}", getHost(), getMappedPort(port)); } private static Path createPwdFile(String login, String pwd) { 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 0552aa3bd..50e7f476a 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 @@ -5,24 +5,20 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; -import static org.assertj.core.api.Assertions.assertThat; - import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; -import io.opentelemetry.contrib.jmxscraper.JmxConnectorBuilder; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; +import io.opentelemetry.contrib.jmxscraper.PortSelector; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingDeque; -import javax.management.remote.JMXConnector; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -35,8 +31,8 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; public abstract class TargetSystemIntegrationTest { - - private static final Logger logger = LoggerFactory.getLogger(TargetSystemIntegrationTest.class); + private static final Logger targetSystemLogger = LoggerFactory.getLogger("TargetSystemContainer"); + private static final Logger jmxScraperLogger = LoggerFactory.getLogger("JmxScraperContainer"); private static final String TARGET_SYSTEM_NETWORK_ALIAS = "targetsystem"; private static String otlpEndpoint; @@ -54,7 +50,6 @@ public abstract class TargetSystemIntegrationTest { private JmxScraperContainer scraper; private static final String OTLP_HOST = "host.testcontainers.internal"; - private static final int JMX_PORT = 9999; @BeforeAll static void beforeAll() { @@ -90,32 +85,20 @@ void afterEach() { @Test void endToEndTest() { + int jmxPort = PortSelector.getAvailableRandomPort(); target = - createTargetContainer(JMX_PORT) - .withLogConsumer(new Slf4jLogConsumer(logger)) + createTargetContainer(jmxPort) + .withLogConsumer(new Slf4jLogConsumer(targetSystemLogger)) .withNetwork(network) - .withExposedPorts(JMX_PORT) .withNetworkAliases(TARGET_SYSTEM_NETWORK_ALIAS); target.start(); - String targetHost = target.getHost(); - Integer targetPort = target.getMappedPort(JMX_PORT); - logger.info( - "Target system started, JMX port: {} mapped to {}:{}", JMX_PORT, targetHost, targetPort); - - // TODO : wait for metrics to be sent and add assertions on what is being captured - // for now we just test that we can connect to remote JMX using our client. - try (JMXConnector connector = JmxConnectorBuilder.createNew(targetHost, targetPort).build()) { - assertThat(connector.getMBeanServerConnection()).isNotNull(); - } catch (IOException e) { - throw new RuntimeException(e); - } - scraper = new JmxScraperContainer(otlpEndpoint) + .withLogConsumer(new Slf4jLogConsumer(jmxScraperLogger)) .withNetwork(network) - .withService(TARGET_SYSTEM_NETWORK_ALIAS, JMX_PORT); + .withService(TARGET_SYSTEM_NETWORK_ALIAS, jmxPort); scraper = customizeScraperContainer(scraper); scraper.start(); From edb3d6eb81243cb63ded9204e36ed4a5952c2343 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:33:25 +0200 Subject: [PATCH 11/21] plug things together + test JVM & Tomcat --- jmx-scraper/build.gradle.kts | 1 + .../jmxscraper/JmxScraperContainer.java | 1 + .../target_systems/JvmIntegrationTest.java | 58 +++++++- .../target_systems/MetricAssertions.java | 126 ++++++++++++++++++ .../TargetSystemIntegrationTest.java | 62 +++++++-- .../target_systems/TomcatIntegrationTest.java | 70 +++++++++- .../contrib/jmxscraper/JmxScraper.java | 2 +- jmx-scraper/src/main/resources/jvm.yaml | 91 +++++++++++++ jmx-scraper/src/main/resources/tomcat.yaml | 16 ++- 9 files changed, 399 insertions(+), 28 deletions(-) create mode 100644 jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/MetricAssertions.java create mode 100644 jmx-scraper/src/main/resources/jvm.yaml diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 8a71277a3..8c2a84675 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { testImplementation("org.junit-pioneer:junit-pioneer") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + testImplementation("org.awaitility:awaitility") } testing { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java index f85a5ba17..a1deb1717 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java @@ -76,6 +76,7 @@ public void start() { // for now only configure through JVM args List<String> arguments = new ArrayList<>(); arguments.add("java"); + arguments.add("-Dotel.metrics.exporter=otlp"); arguments.add("-Dotel.exporter.otlp.endpoint=" + endpoint); if (!targetSystems.isEmpty()) { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java index 4c240ee16..2b89914f3 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java @@ -5,9 +5,13 @@ 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.assertTypedGauge; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertTypedSum; + import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; import io.opentelemetry.contrib.jmxscraper.TestAppContainer; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; +import java.util.Arrays; import java.util.List; import org.testcontainers.containers.GenericContainer; @@ -25,7 +29,55 @@ protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scra } @Override - protected void verifyMetrics(List<ExportMetricsServiceRequest> metrics) { - // TODO: Verify gathered metrics + protected void verifyMetrics() { + // those values depend on the JVM GC configured + List<String> gcLabels = + Arrays.asList( + "Code Cache", + "PS Eden Space", + "PS Old Gen", + "Metaspace", + "Compressed Class Space", + "PS Survivor Space"); + List<String> gcCollectionLabels = Arrays.asList("PS MarkSweep", "PS Scavenge"); + + waitAndAssertMetrics( + metric -> assertGauge(metric, "jvm.classes.loaded", "number of loaded classes", "1"), + metric -> + assertTypedSum( + metric, + "jvm.gc.collections.count", + "total number of collections that have occurred", + "1", + gcCollectionLabels), + metric -> + assertTypedSum( + metric, + "jvm.gc.collections.elapsed", + "the approximate accumulated collection elapsed time in milliseconds", + "ms", + gcCollectionLabels), + metric -> assertGauge(metric, "jvm.memory.heap.committed", "current heap usage", "by"), + metric -> assertGauge(metric, "jvm.memory.heap.init", "current heap usage", "by"), + metric -> assertGauge(metric, "jvm.memory.heap.max", "current heap usage", "by"), + metric -> assertGauge(metric, "jvm.memory.heap.used", "current heap usage", "by"), + metric -> + assertGauge(metric, "jvm.memory.nonheap.committed", "current non-heap usage", "by"), + metric -> assertGauge(metric, "jvm.memory.nonheap.init", "current non-heap usage", "by"), + metric -> assertGauge(metric, "jvm.memory.nonheap.max", "current non-heap usage", "by"), + metric -> assertGauge(metric, "jvm.memory.nonheap.used", "current non-heap usage", "by"), + metric -> + assertTypedGauge( + metric, "jvm.memory.pool.committed", "current memory pool usage", "by", gcLabels), + metric -> + assertTypedGauge( + metric, "jvm.memory.pool.init", "current memory pool usage", "by", gcLabels), + metric -> + assertTypedGauge( + metric, "jvm.memory.pool.max", "current memory pool usage", "by", gcLabels), + metric -> + assertTypedGauge( + metric, "jvm.memory.pool.used", "current memory pool usage", "by", gcLabels), + metric -> assertGauge(metric, "jvm.threads.count", "number of threads", "1")); } } 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 new file mode 100644 index 000000000..addf145ea --- /dev/null +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/MetricAssertions.java @@ -0,0 +1,126 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.jmxscraper.target_systems; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import io.opentelemetry.proto.common.v1.KeyValue; +import io.opentelemetry.proto.metrics.v1.Metric; +import io.opentelemetry.proto.metrics.v1.NumberDataPoint; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import org.assertj.core.api.MapAssert; + +/** Metrics assertions */ +class MetricAssertions { + + private MetricAssertions() {} + + static void assertGauge(Metric metric, String name, String description, String unit) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasGauge()).isTrue(); + assertThat(metric.getGauge().getDataPointsList()) + .satisfiesExactly(point -> assertThat(point.getAttributesList()).isEmpty()); + } + + static void assertSum(Metric metric, String name, String description, String unit) { + assertSum(metric, name, description, unit, /* isMonotonic= */ true); + } + + static void assertSum( + Metric metric, String name, String description, String unit, boolean isMonotonic) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasSum()).isTrue(); + assertThat(metric.getSum().getDataPointsList()) + .satisfiesExactly(point -> assertThat(point.getAttributesList()).isEmpty()); + assertThat(metric.getSum().getIsMonotonic()).isEqualTo(isMonotonic); + } + + static void assertTypedGauge( + Metric metric, String name, String description, String unit, List<String> types) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasGauge()).isTrue(); + assertTypedPoints(metric.getGauge().getDataPointsList(), types); + } + + static void assertTypedSum( + Metric metric, String name, String description, String unit, List<String> types) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasSum()).isTrue(); + assertTypedPoints(metric.getSum().getDataPointsList(), types); + } + + @SafeVarargs + static void assertSumWithAttributes( + Metric metric, + String name, + String description, + String unit, + Consumer<MapAssert<String, String>>... attributeGroupAssertions) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasSum()).isTrue(); + assertAttributedPoints(metric.getSum().getDataPointsList(), attributeGroupAssertions); + } + + @SafeVarargs + static void assertGaugeWithAttributes( + Metric metric, + String name, + String description, + String unit, + Consumer<MapAssert<String, String>>... attributeGroupAssertions) { + assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getDescription()).isEqualTo(description); + assertThat(metric.getUnit()).isEqualTo(unit); + assertThat(metric.hasGauge()).isTrue(); + assertAttributedPoints(metric.getGauge().getDataPointsList(), attributeGroupAssertions); + } + + @SuppressWarnings("unchecked") + private static void assertTypedPoints(List<NumberDataPoint> points, List<String> types) { + Consumer<MapAssert<String, String>>[] assertions = + types.stream() + .map( + type -> + (Consumer<MapAssert<String, String>>) + attrs -> attrs.containsOnly(entry("name", type))) + .toArray(Consumer[]::new); + + assertAttributedPoints(points, assertions); + } + + @SuppressWarnings("unchecked") + private static void assertAttributedPoints( + List<NumberDataPoint> points, + Consumer<MapAssert<String, String>>... attributeGroupAssertions) { + Consumer<Map<String, String>>[] assertions = + Arrays.stream(attributeGroupAssertions) + .map(assertion -> (Consumer<Map<String, String>>) m -> assertion.accept(assertThat(m))) + .toArray(Consumer[]::new); + assertThat(points) + .extracting( + numberDataPoint -> + numberDataPoint.getAttributesList().stream() + .collect( + Collectors.toMap( + KeyValue::getKey, keyValue -> keyValue.getValue().getStringValue()))) + .satisfiesExactlyInAnyOrder(assertions); + } +} 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 8ab2241f8..383197a9e 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 @@ -6,23 +6,28 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; -import io.opentelemetry.contrib.jmxscraper.JmxConnectorBuilder; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; -import java.io.IOException; +import io.opentelemetry.proto.metrics.v1.Metric; +import io.opentelemetry.proto.metrics.v1.ResourceMetrics; +import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingDeque; -import javax.management.remote.JMXConnector; +import java.util.function.Consumer; +import java.util.stream.Collectors; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -104,14 +109,6 @@ void endToEndTest() { logger.info( "Target system started, JMX port: {} mapped to {}:{}", JMX_PORT, targetHost, targetPort); - // TODO : wait for metrics to be sent and add assertions on what is being captured - // for now we just test that we can connect to remote JMX using our client. - try (JMXConnector connector = JmxConnectorBuilder.createNew(targetHost, targetPort).build()) { - assertThat(connector.getMBeanServerConnection()).isNotNull(); - } catch (IOException e) { - throw new RuntimeException(e); - } - scraper = new JmxScraperContainer(otlpEndpoint) .withLogConsumer(new Slf4jLogConsumer(logger).withPrefix("jmx-scraper")) @@ -121,10 +118,45 @@ void endToEndTest() { scraper = customizeScraperContainer(scraper); scraper.start(); - verifyMetrics(otlpServer.getMetrics()); + verifyMetrics(); } - protected abstract void verifyMetrics(List<ExportMetricsServiceRequest> metrics); + protected void waitAndAssertMetrics(Iterable<Consumer<Metric>> assertions) { + await() + .atMost(Duration.ofSeconds(30)) + .untilAsserted( + () -> { + List<ExportMetricsServiceRequest> receivedMetrics = otlpServer.getMetrics(); + assertThat(receivedMetrics).describedAs("no metric received").isNotEmpty(); + + List<Metric> metrics = + receivedMetrics.stream() + .map(ExportMetricsServiceRequest::getResourceMetricsList) + .flatMap(rm -> rm.stream().map(ResourceMetrics::getScopeMetricsList)) + .flatMap(Collection::stream) + .filter( + // TODO: disabling batch span exporter might help remove unwanted metrics + sm -> sm.getScope().getName().equals("io.opentelemetry.jmx")) + .flatMap(sm -> sm.getMetricsList().stream()) + .collect(Collectors.toList()); + + assertThat(metrics) + .describedAs("metrics reported but none from JMX scraper") + .isNotEmpty(); + + for (Consumer<Metric> assertion : assertions) { + assertThat(metrics).anySatisfy(assertion); + } + }); + } + + @SafeVarargs + @SuppressWarnings("varargs") + protected final void waitAndAssertMetrics(Consumer<Metric>... assertions) { + waitAndAssertMetrics(Arrays.asList(assertions)); + } + + protected abstract void verifyMetrics(); protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scraper) { return scraper; @@ -153,6 +185,10 @@ protected void configure(ServerBuilder sb) { public void export( ExportMetricsServiceRequest request, StreamObserver<ExportMetricsServiceResponse> responseObserver) { + + // verbose but helpful to diagnose what is received + logger.info("receiving metrics {}", request); + metricRequests.add(request); responseObserver.onNext(ExportMetricsServiceResponse.getDefaultInstance()); responseObserver.onCompleted(); 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 ccf7e59a7..500e89f82 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 @@ -5,10 +5,12 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertGaugeWithAttributes; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertSumWithAttributes; +import static org.assertj.core.api.Assertions.entry; + import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; -import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import java.time.Duration; -import java.util.List; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; @@ -48,7 +50,67 @@ protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scra } @Override - protected void verifyMetrics(List<ExportMetricsServiceRequest> metrics) { - // TODO: Verify gathered metrics + protected void verifyMetrics() { + waitAndAssertMetrics( + metric -> + assertGaugeWithAttributes( + metric, + "tomcat.sessions", + "The number of active sessions", + "sessions", + attrs -> attrs.containsKey("context")), + metric -> + assertSumWithAttributes( + metric, + "tomcat.errors", + "The number of errors encountered", + "errors", + attrs -> attrs.containsOnly(entry("proto_handler", "\"http-nio-8080\""))), + metric -> + assertSumWithAttributes( + metric, + "tomcat.processing_time", + "The total processing time", + "ms", + attrs -> attrs.containsOnly(entry("proto_handler", "\"http-nio-8080\""))), + metric -> + assertSumWithAttributes( + metric, + "tomcat.traffic", + "The number of bytes transmitted and received", + "by", + attrs -> + attrs.containsOnly( + entry("proto_handler", "\"http-nio-8080\""), entry("direction", "sent")), + attrs -> + attrs.containsOnly( + entry("proto_handler", "\"http-nio-8080\""), + entry("direction", "received"))), + metric -> + assertGaugeWithAttributes( + metric, + "tomcat.threads", + "The number of threads", + "threads", + attrs -> + attrs.containsOnly( + entry("proto_handler", "\"http-nio-8080\""), entry("state", "idle")), + attrs -> + attrs.containsOnly( + entry("proto_handler", "\"http-nio-8080\""), entry("state", "busy"))), + metric -> + assertGaugeWithAttributes( + metric, + "tomcat.max_time", + "Maximum time to process a request", + "ms", + attrs -> attrs.containsOnly(entry("proto_handler", "\"http-nio-8080\""))), + metric -> + assertSumWithAttributes( + metric, + "tomcat.request_count", + "The total requests", + "requests", + attrs -> attrs.containsOnly(entry("proto_handler", "\"http-nio-8080\"")))); } } diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index b1faa0854..ba84aac2d 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -166,7 +166,7 @@ private void start() throws IOException { private static MetricConfiguration getMetricConfig(JmxScraperConfig scraperConfig) { MetricConfiguration config = new MetricConfiguration(); for (String system : scraperConfig.getTargetSystems()) { - addRulesForSystem(system, config); + addRulesForSystem(system, config); } // TODO : add ability for user to provide custom yaml configurations return config; diff --git a/jmx-scraper/src/main/resources/jvm.yaml b/jmx-scraper/src/main/resources/jvm.yaml new file mode 100644 index 000000000..d3e95d5d4 --- /dev/null +++ b/jmx-scraper/src/main/resources/jvm.yaml @@ -0,0 +1,91 @@ +--- + +rules: + + - bean: java.lang:type=ClassLoading + mapping: + LoadedClassCount: + metric: jvm.classes.loaded + type: gauge + unit: '1' + desc: number of loaded classes + + - bean: java.lang:type=GarbageCollector,name=* + mapping: + CollectionCount: + metric: jvm.gc.collections.count + type: counter + unit: '1' + desc: total number of collections that have occurred + metricAttribute: + name: param(name) + CollectionTime: + metric: jvm.gc.collections.elapsed + type: counter + unit: ms + desc: the approximate accumulated collection elapsed time in milliseconds + metricAttribute: + name: param(name) + + - bean: java.lang:type=Memory + unit: by + prefix: jvm.memory. + mapping: + HeapMemoryUsage.committed: + metric: heap.committed + desc: current heap usage + type: gauge + HeapMemoryUsage.init: + metric: heap.init + desc: current heap usage + type: gauge + HeapMemoryUsage.max: + metric: heap.max + desc: current heap usage + type: gauge + HeapMemoryUsage.used: + metric: heap.used + desc: current heap usage + type: gauge + NonHeapMemoryUsage.committed: + metric: nonheap.committed + desc: current non-heap usage + type: gauge + NonHeapMemoryUsage.init: + metric: nonheap.init + desc: current non-heap usage + type: gauge + NonHeapMemoryUsage.max: + metric: nonheap.max + desc: current non-heap usage + type: gauge + NonHeapMemoryUsage.used: + metric: nonheap.used + desc: current non-heap usage + type: gauge + + - bean: java.lang:type=MemoryPool,name=* + type: gauge + unit: by + metricAttribute: + name: param(name) + mapping: + Usage.committed: + metric: jvm.memory.pool.committed + desc: current memory pool usage + Usage.init: + metric: jvm.memory.pool.init + desc: current memory pool usage + Usage.max: + metric: jvm.memory.pool.max + desc: current memory pool usage + Usage.used: + metric: jvm.memory.pool.used + desc: current memory pool usage + + - bean: java.lang:type=Threading + mapping: + ThreadCount: + metric: jvm.threads.count + unit: '1' + desc: number of threads diff --git a/jmx-scraper/src/main/resources/tomcat.yaml b/jmx-scraper/src/main/resources/tomcat.yaml index 0c60bf49d..076be6400 100644 --- a/jmx-scraper/src/main/resources/tomcat.yaml +++ b/jmx-scraper/src/main/resources/tomcat.yaml @@ -13,7 +13,7 @@ rules: context: param(context) mapping: activeSessions: - metric: sessions + metric: tomcat.sessions type: gauge unit: sessions desc: The number of active sessions @@ -39,24 +39,24 @@ rules: metric: max_time type: gauge unit: ms - desc: The total requests + desc: Maximum time to process a request processingTime: metric: processing_time - type: gauge + type: counter unit: ms desc: The total processing time bytesSent: metric: traffic type: counter unit: by - desc: The number of bytes transmitted + desc: The number of bytes transmitted and received metricAttribute: direction: const(sent) bytesReceived: metric: traffic type: counter unit: by - desc: The number of bytes received + desc: The number of bytes transmitted and received metricAttribute: direction: const(received) @@ -69,13 +69,15 @@ rules: mapping: currentThreadCount: metric: threads - type: updowncounter + desc: The number of threads + type: gauge unit: threads metricAttribute: state: const(idle) currentThreadsBusy: metric: threads - type: updowncounter + desc: The number of threads + type: gauge unit: threads metricAttribute: state: const(busy) From 70d0bb0a77d60eded4d0870220e0f5328596b1f3 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:58:39 +0200 Subject: [PATCH 12/21] change case of exceptions msg --- .../io/opentelemetry/contrib/jmxscraper/JmxScraper.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index ba84aac2d..1ad51893e 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -97,10 +97,10 @@ static Properties parseArgs(List<String> args) return new Properties(); } if (args.size() != 2) { - throw new ArgumentsParsingException("exactly two arguments expected, got " + args.size()); + throw new ArgumentsParsingException("Exactly two arguments expected, got " + args.size()); } if (!args.get(0).equalsIgnoreCase(CONFIG_ARG)) { - throw new ArgumentsParsingException("unexpected first argument must be '" + CONFIG_ARG + "'"); + throw new ArgumentsParsingException("Unexpected first argument must be '" + CONFIG_ARG + "'"); } String path = args.get(1); @@ -180,10 +180,10 @@ private static void addRulesForSystem(String system, MetricConfiguration conf) { RuleParser parserInstance = RuleParser.get(); parserInstance.addMetricDefsTo(conf, inputStream, system); } else { - throw new IllegalStateException("no support for system" + system); + throw new IllegalArgumentException("No support for system" + system); } } catch (Exception e) { - throw new IllegalStateException("error while loading rules for system " + system, e); + throw new IllegalStateException("Error while loading rules for system " + system, e); } } } From 12e65333a0cb3f4f99763288a998267abd84d080 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:57:01 +0200 Subject: [PATCH 13/21] refactor in a single method --- .../jmxscraper/JmxConnectorBuilderTest.java | 5 ++- .../contrib/jmxscraper/TestAppContainer.java | 32 +++++++++++++------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index f6589cc10..63483fd00 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -33,7 +33,7 @@ static void afterAll() { void noAuth() { int port = PortSelector.getAvailableRandomPort(); try (TestAppContainer app = - new TestAppContainer().withJmxPort(port).withJmxAccessibleFromHost()) { + new TestAppContainer().withHostAccessFixedJmxPort(port)) { app.start(); testConnector( () -> JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port)).build()); @@ -47,8 +47,7 @@ void loginPwdAuth() { String pwd = "t0p!Secret"; try (TestAppContainer app = new TestAppContainer() - .withJmxPort(port) - .withJmxAccessibleFromHost() + .withHostAccessFixedJmxPort(port) .withUserAuth(login, pwd)) { app.start(); testConnector( diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java index 663a73bd6..c95eba3e7 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java @@ -43,17 +43,15 @@ public TestAppContainer() { .withCommand("java", "-jar", "/app.jar"); } + /** + * Configures app container for container-to-container access + * + * @param port mapped port to use + * @return this + */ @CanIgnoreReturnValue public TestAppContainer withJmxPort(int port) { properties.put("com.sun.management.jmxremote.port", Integer.toString(port)); - properties.put("com.sun.management.jmxremote.rmi.port", Integer.toString(port)); - - // To get host->container JMX connection working docker must expose JMX/RMI port under the same - // port number. Because of this testcontainers' standard exposed port randomization approach - // can't be used. - // Explanation: - // https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/6 - addFixedExposedPort(port, port); return this; } @@ -64,9 +62,25 @@ public TestAppContainer withUserAuth(String login, String pwd) { return this; } + /** + * Configures app container for host-to-container access, port will be used as-is from host to + * work-around JMX in docker. This is optional on Linux as there is a network route and the container + * is accessible, but not on Mac where the container runs in an isolated VM. + * + * @param port port to use, must be available on host. + * @return this + */ @CanIgnoreReturnValue - public TestAppContainer withJmxAccessibleFromHost() { + public TestAppContainer withHostAccessFixedJmxPort(int port) { + // To get host->container JMX connection working docker must expose JMX/RMI port under the same + // port number. Because of this testcontainers' standard exposed port randomization approach + // can't be used. + // Explanation: + // https://forums.docker.com/t/exposing-mapped-jmx-ports-from-multiple-containers/5287/6 + properties.put("com.sun.management.jmxremote.port", Integer.toString(port)); + properties.put("com.sun.management.jmxremote.rmi.port", Integer.toString(port)); properties.put("java.rmi.server.hostname", getHost()); + addFixedExposedPort(port, port); return this; } From 172846c5f2460e2c4578574b8e4404b87701e784 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:14:09 +0200 Subject: [PATCH 14/21] cleanup and reuse free port impl. --- .../jmxscraper/JmxConnectorBuilderTest.java | 12 +++--- .../contrib/jmxscraper/PortSelector.java | 42 ------------------- .../contrib/jmxscraper/TestAppContainer.java | 4 +- .../TargetSystemIntegrationTest.java | 4 +- 4 files changed, 9 insertions(+), 53 deletions(-) delete mode 100644 jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index 63483fd00..c785f41ee 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.linecorp.armeria.internal.common.util.PortUtil; import java.io.IOException; import javax.management.ObjectName; import javax.management.remote.JMXConnector; @@ -31,9 +32,8 @@ static void afterAll() { @Test void noAuth() { - int port = PortSelector.getAvailableRandomPort(); - try (TestAppContainer app = - new TestAppContainer().withHostAccessFixedJmxPort(port)) { + int port = PortUtil.unusedTcpPort(); + try (TestAppContainer app = new TestAppContainer().withHostAccessFixedJmxPort(port)) { app.start(); testConnector( () -> JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port)).build()); @@ -42,13 +42,11 @@ void noAuth() { @Test void loginPwdAuth() { - int port = PortSelector.getAvailableRandomPort(); + int port = PortUtil.unusedTcpPort(); String login = "user"; String pwd = "t0p!Secret"; try (TestAppContainer app = - new TestAppContainer() - .withHostAccessFixedJmxPort(port) - .withUserAuth(login, pwd)) { + new TestAppContainer().withHostAccessFixedJmxPort(port).withUserAuth(login, pwd)) { app.start(); testConnector( () -> diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java deleted file mode 100644 index 400b7bb5e..000000000 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.jmxscraper; - -import java.io.IOException; -import java.net.Socket; -import java.util.Random; - -/** Class used for finding random free network port from range 1024-65535 */ -public class PortSelector { - private static final Random random = new Random(System.currentTimeMillis()); - - private static final int MIN_PORT = 1024; - private static final int MAX_PORT = 65535; - - private PortSelector() {} - - /** - * @return random available TCP port - */ - public static synchronized int getAvailableRandomPort() { - int port; - - do { - port = random.nextInt(MAX_PORT - MIN_PORT + 1) + MIN_PORT; - } while (!isPortAvailable(port)); - - return port; - } - - private static boolean isPortAvailable(int port) { - // see https://stackoverflow.com/a/13826145 for the chosen implementation - try (Socket s = new Socket("localhost", port)) { - return false; - } catch (IOException e) { - return true; - } - } -} diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java index c95eba3e7..be92a0a96 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/TestAppContainer.java @@ -64,8 +64,8 @@ public TestAppContainer withUserAuth(String login, String pwd) { /** * Configures app container for host-to-container access, port will be used as-is from host to - * work-around JMX in docker. This is optional on Linux as there is a network route and the container - * is accessible, but not on Mac where the container runs in an isolated VM. + * work-around JMX in docker. This is optional on Linux as there is a network route and the + * container is accessible, but not on Mac where the container runs in an isolated VM. * * @param port port to use, must be available on host. * @return this 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 50e7f476a..1dba3d70a 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 @@ -5,12 +5,12 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; +import com.linecorp.armeria.internal.common.util.PortUtil; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; -import io.opentelemetry.contrib.jmxscraper.PortSelector; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; @@ -85,7 +85,7 @@ void afterEach() { @Test void endToEndTest() { - int jmxPort = PortSelector.getAvailableRandomPort(); + int jmxPort = PortUtil.unusedTcpPort(); target = createTargetContainer(jmxPort) From 898c3c8500feee04910d07fd16dcf6ee0b93af9e Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:28:42 +0200 Subject: [PATCH 15/21] revert port selector class --- .../jmxscraper/JmxConnectorBuilderTest.java | 5 +-- .../contrib/jmxscraper/PortSelector.java | 42 +++++++++++++++++++ .../TargetSystemIntegrationTest.java | 4 +- 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java index c785f41ee..5766cd890 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilderTest.java @@ -7,7 +7,6 @@ import static org.assertj.core.api.Assertions.assertThat; -import com.linecorp.armeria.internal.common.util.PortUtil; import java.io.IOException; import javax.management.ObjectName; import javax.management.remote.JMXConnector; @@ -32,7 +31,7 @@ static void afterAll() { @Test void noAuth() { - int port = PortUtil.unusedTcpPort(); + int port = PortSelector.getAvailableRandomPort(); try (TestAppContainer app = new TestAppContainer().withHostAccessFixedJmxPort(port)) { app.start(); testConnector( @@ -42,7 +41,7 @@ void noAuth() { @Test void loginPwdAuth() { - int port = PortUtil.unusedTcpPort(); + int port = PortSelector.getAvailableRandomPort(); String login = "user"; String pwd = "t0p!Secret"; try (TestAppContainer app = diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java new file mode 100644 index 000000000..400b7bb5e --- /dev/null +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/PortSelector.java @@ -0,0 +1,42 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.jmxscraper; + +import java.io.IOException; +import java.net.Socket; +import java.util.Random; + +/** Class used for finding random free network port from range 1024-65535 */ +public class PortSelector { + private static final Random random = new Random(System.currentTimeMillis()); + + private static final int MIN_PORT = 1024; + private static final int MAX_PORT = 65535; + + private PortSelector() {} + + /** + * @return random available TCP port + */ + public static synchronized int getAvailableRandomPort() { + int port; + + do { + port = random.nextInt(MAX_PORT - MIN_PORT + 1) + MIN_PORT; + } while (!isPortAvailable(port)); + + return port; + } + + private static boolean isPortAvailable(int port) { + // see https://stackoverflow.com/a/13826145 for the chosen implementation + try (Socket s = new Socket("localhost", port)) { + return false; + } catch (IOException e) { + return true; + } + } +} 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 1dba3d70a..50e7f476a 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 @@ -5,12 +5,12 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; -import com.linecorp.armeria.internal.common.util.PortUtil; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; +import io.opentelemetry.contrib.jmxscraper.PortSelector; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; @@ -85,7 +85,7 @@ void afterEach() { @Test void endToEndTest() { - int jmxPort = PortUtil.unusedTcpPort(); + int jmxPort = PortSelector.getAvailableRandomPort(); target = createTargetContainer(jmxPort) From 2260258dae67e2f81566c3167f57715d4229727a Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:43:46 +0200 Subject: [PATCH 16/21] use fixed port for container-to-container --- .../target_systems/TargetSystemIntegrationTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 50e7f476a..e4542ce4b 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 @@ -51,6 +51,10 @@ public abstract class TargetSystemIntegrationTest { private static final String OTLP_HOST = "host.testcontainers.internal"; + // JMX communication only happens between container, and we don't have to use JMX + // from host to container, we can use a fixed port. + private static final int JMX_PORT = 9999; + @BeforeAll static void beforeAll() { network = Network.newNetwork(); @@ -85,10 +89,9 @@ void afterEach() { @Test void endToEndTest() { - int jmxPort = PortSelector.getAvailableRandomPort(); target = - createTargetContainer(jmxPort) + createTargetContainer(JMX_PORT) .withLogConsumer(new Slf4jLogConsumer(targetSystemLogger)) .withNetwork(network) .withNetworkAliases(TARGET_SYSTEM_NETWORK_ALIAS); @@ -98,7 +101,7 @@ void endToEndTest() { new JmxScraperContainer(otlpEndpoint) .withLogConsumer(new Slf4jLogConsumer(jmxScraperLogger)) .withNetwork(network) - .withService(TARGET_SYSTEM_NETWORK_ALIAS, jmxPort); + .withService(TARGET_SYSTEM_NETWORK_ALIAS, JMX_PORT); scraper = customizeScraperContainer(scraper); scraper.start(); From df4e1498777fed7eaa56238c7448086e701c7dd8 Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Fri, 11 Oct 2024 10:30:08 +0200 Subject: [PATCH 17/21] Spotless fix --- .../jmxscraper/target_systems/TargetSystemIntegrationTest.java | 1 - 1 file changed, 1 deletion(-) 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 e4542ce4b..2cce282f9 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 @@ -10,7 +10,6 @@ import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; -import io.opentelemetry.contrib.jmxscraper.PortSelector; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; From 055813c066d6f75a4a3c72fe39376e802a1532d0 Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Tue, 15 Oct 2024 15:18:04 +0200 Subject: [PATCH 18/21] ActiveMQ Classic integration test --- .../ActiveMqIntegrationTest.java | 156 ++++++++++++++++++ .../target_systems/MetricAssertions.java | 13 ++ .../TargetSystemIntegrationTest.java | 11 +- .../contrib/jmxscraper/JmxScraper.java | 4 +- jmx-scraper/src/main/resources/activemq.yaml | 69 ++++++++ jmx-scraper/src/main/resources/activemq/env | 117 +++++++++++++ 6 files changed, 365 insertions(+), 5 deletions(-) create mode 100644 jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java create mode 100644 jmx-scraper/src/main/resources/activemq.yaml create mode 100644 jmx-scraper/src/main/resources/activemq/env 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 new file mode 100644 index 000000000..1a79469ee --- /dev/null +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java @@ -0,0 +1,156 @@ +/* + * 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.assertGaugeWithAttributes; +import static io.opentelemetry.contrib.jmxscraper.target_systems.MetricAssertions.assertSumWithAttributes; +import static org.assertj.core.api.Assertions.entry; + +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; +import org.testcontainers.utility.MountableFile; + +public class ActiveMqIntegrationTest extends TargetSystemIntegrationTest { + + @Override + protected GenericContainer<?> createTargetContainer(int jmxPort) { + return new GenericContainer<>( + new ImageFromDockerfile() + .withDockerfileFromBuilder( + builder -> builder.from("apache/activemq-classic:5.18.6").build())) + .withEnv("LOCAL_JMX", "no") + .withCopyFileToContainer( + MountableFile.forClasspathResource("activemq/env"), "/opt/apache-activemq/bin/env") + .withEnv( + "ACTIVEMQ_JMX_OPTS", + "-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") + .withStartupTimeout(Duration.ofMinutes(2)) + .waitingFor(Wait.forListeningPort()); + } + + @Override + protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scraper) { + return scraper.withTargetSystem("activemq"); + } + + @Override + protected void verifyMetrics() { + waitAndAssertMetrics( + metric -> + assertSumWithAttributes( + metric, + "activemq.consumer.count", + "The number of consumers currently reading from the broker.", + "consumers", + /* isMonotonic= */ false, + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertSumWithAttributes( + metric, + "activemq.producer.count", + "The number of producers currently attached to the broker.", + "producers", + /* isMonotonic= */ false, + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertSumWithAttributes( + metric, + "activemq.connection.count", + "The total number of current connections.", + "connections", + /* isMonotonic= */ false, + attrs -> attrs.containsOnly(entry("broker", "localhost"))), + metric -> + assertGaugeWithAttributes( + metric, + "activemq.memory.usage", + "The percentage of configured memory used.", + "%", + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertGaugeWithAttributes( + metric, + "activemq.disk.store_usage", + "The percentage of configured disk used for persistent messages.", + "%", + attrs -> attrs.containsOnly(entry("broker", "localhost"))), + metric -> + assertGaugeWithAttributes( + metric, + "activemq.disk.temp_usage", + "The percentage of configured disk used for non-persistent messages.", + "%", + attrs -> attrs.containsOnly(entry("broker", "localhost"))), + metric -> + assertSumWithAttributes( + metric, + "activemq.message.current", + "The current number of messages waiting to be consumed.", + "messages", + /* isMonotonic= */ false, + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertSumWithAttributes( + metric, + "activemq.message.expired", + "The total number of messages not delivered because they expired.", + "messages", + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertSumWithAttributes( + metric, + "activemq.message.enqueued", + "The total number of messages received by the broker.", + "messages", + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertSumWithAttributes( + metric, + "activemq.message.dequeued", + "The total number of messages delivered to consumers.", + "messages", + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost"))), + metric -> + assertGaugeWithAttributes( + metric, + "activemq.message.wait_time.avg", + "The average time a message was held on a destination.", + "ms", + attrs -> + attrs.containsOnly( + entry("destination", "ActiveMQ.Advisory.MasterBroker"), + entry("broker", "localhost")))); + } +} 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 addf145ea..cbd46c350 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 @@ -72,10 +72,23 @@ static void assertSumWithAttributes( String description, String unit, Consumer<MapAssert<String, String>>... attributeGroupAssertions) { + assertSumWithAttributes( + metric, name, description, unit, /* isMonotonic= */ true, attributeGroupAssertions); + } + + @SafeVarargs + static void assertSumWithAttributes( + Metric metric, + String name, + String description, + String unit, + boolean isMonotonic, + Consumer<MapAssert<String, String>>... 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); assertAttributedPoints(metric.getSum().getDataPointsList(), attributeGroupAssertions); } 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 808f8c429..383197a9e 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 @@ -6,23 +6,28 @@ package io.opentelemetry.contrib.jmxscraper.target_systems; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; import com.linecorp.armeria.testing.junit5.server.ServerExtension; import io.grpc.stub.StreamObserver; -import io.opentelemetry.contrib.jmxscraper.JmxConnectorBuilder; import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceResponse; import io.opentelemetry.proto.collector.metrics.v1.MetricsServiceGrpc; -import java.io.IOException; +import io.opentelemetry.proto.metrics.v1.Metric; +import io.opentelemetry.proto.metrics.v1.ResourceMetrics; +import java.time.Duration; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.LinkedBlockingDeque; -import javax.management.remote.JMXConnector; +import java.util.function.Consumer; +import java.util.stream.Collectors; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index 1ad51893e..a38912ba8 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -78,7 +78,7 @@ public static void main(String[] args) { System.err.println("Unable to connect " + e.getMessage()); System.exit(2); } catch (RuntimeException e) { - System.err.println("ERROR: " + e.getMessage()); + e.printStackTrace(System.err); System.exit(3); } } @@ -180,7 +180,7 @@ private static void addRulesForSystem(String system, MetricConfiguration conf) { RuleParser parserInstance = RuleParser.get(); parserInstance.addMetricDefsTo(conf, inputStream, system); } else { - throw new IllegalArgumentException("No support for system" + system); + throw new IllegalArgumentException("No support for system " + system); } } catch (Exception e) { throw new IllegalStateException("Error while loading rules for system " + system, e); diff --git a/jmx-scraper/src/main/resources/activemq.yaml b/jmx-scraper/src/main/resources/activemq.yaml new file mode 100644 index 000000000..044488dfb --- /dev/null +++ b/jmx-scraper/src/main/resources/activemq.yaml @@ -0,0 +1,69 @@ +--- +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: + metric: producer.count + unit: "producers" + type: updowncounter + desc: The number of producers currently attached to the broker. + ConsumerCount: + metric: consumer.count + unit: "consumers" + type: updowncounter + desc: The number of consumers currently reading from the broker. + MemoryPercentUsage: + metric: memory.usage + unit: "%" + type: gauge + desc: The percentage of configured memory used. + QueueSize: + metric: message.current + unit: "messages" + type: updowncounter + desc: The current number of messages waiting to be consumed. + ExpiredCount: + metric: message.expired + unit: "messages" + type: counter + desc: The total number of messages not delivered because they expired. + EnqueueCount: + metric: message.enqueued + unit: "messages" + type: counter + desc: The total number of messages received by the broker. + DequeueCount: + metric: message.dequeued + unit: "messages" + type: counter + desc: The total number of messages delivered to consumers. + AverageEnqueueTime: + metric: message.wait_time.avg + unit: ms + type: gauge + desc: The average time a message was held on a destination. + + - bean: org.apache.activemq:type=Broker,brokerName=* + metricAttribute: + broker: param(brokerName) + prefix: activemq. + unit: "%" + type: gauge + mapping: + CurrentConnectionsCount: + metric: connection.count + type: updowncounter + unit: "connections" + desc: The total number of current connections. + StorePercentUsage: + metric: disk.store_usage + desc: The percentage of configured disk used for persistent messages. + TempPercentUsage: + metric: disk.temp_usage + desc: The percentage of configured disk used for non-persistent messages. diff --git a/jmx-scraper/src/main/resources/activemq/env b/jmx-scraper/src/main/resources/activemq/env new file mode 100644 index 000000000..bfcc87c6f --- /dev/null +++ b/jmx-scraper/src/main/resources/activemq/env @@ -0,0 +1,117 @@ + #!/bin/sh + # ------------------------------------------------------------------------ + # Licensed to the Apache Software Foundation (ASF) under one or more + # contributor license agreements. See the NOTICE file distributed with + # this work for additional information regarding copyright ownership. + # The ASF 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. + # ------------------------------------------------------------------------ + # + # Configuration file for running Apache Active MQ as standalone provider. + # + # This file overwrites the predefined settings of the sysv init-script. + # You can also use alternate location for default settings - + # invoke the init-script without a argument an review help section "Configuration of this script" + # /etc/default/activemq <activemq user home>/.activemqrc <activemq installation dir>/bin/env + + # Active MQ installation dirs + # ACTIVEMQ_HOME="<Installationdir>/" + # ACTIVEMQ_BASE="$ACTIVEMQ_HOME" + # ACTIVEMQ_CONF="$ACTIVEMQ_BASE/conf" + # ACTIVEMQ_DATA="$ACTIVEMQ_BASE/data" + # ACTIVEMQ_TMP="$ACTIVEMQ_BASE/tmp" + + # Set jvm memory configuration (minimal/maximum amount of memory) + ACTIVEMQ_OPTS_MEMORY="-Xms64M -Xmx256M" + + if [ -z "$ACTIVEMQ_OPTS" ] ; then + ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS_MEMORY -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=$ACTIVEMQ_CONF/login.config" + fi + + if [ -z "$ACTIVEMQ_OUT" ]; then + ACTIVEMQ_OUT="/dev/null" + fi + + # Uncomment to enable audit logging + #ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS -Dorg.apache.activemq.audit=true" + + # Set jvm jmx configuration + # This enables jmx access over a configured jmx-tcp-port. + # You have to configure the first four settings if you run a ibm jvm, caused by the + # fact that IBM's jvm does not support VirtualMachine.attach(PID). + # JMX access is needed for quering a running activemq instance to gain data or to + # trigger management operations. + # + # Example for ${ACTIVEMQ_CONF}/jmx.access: + # --- + # # The "monitorRole" role has readonly access. + # # The "controlRole" role has readwrite access. + # monitorRole readonly + # controlRole readwrite + # --- + # + # Example for ${ACTIVEMQ_CONF}/jmx.password: + # --- + # # The "monitorRole" role has password "abc123". + # # # The "controlRole" role has password "abcd1234". + # monitorRole abc123 + # controlRole abcd1234 + # --- + # + # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.port=11099 " + # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.password.file=${ACTIVEMQ_CONF}/jmx.password" + # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.access.file=${ACTIVEMQ_CONF}/jmx.access" + # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.ssl=false" + # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote" + ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote" + + # Set jvm jmx configuration for controlling the broker process + # You only have to configure the first four settings if you run a ibm jvm, caused by the + # fact that IBM's jvm does not support VirtualMachine.attach(PID) + # (see also com.sun.management.jmxremote.port, .jmx.password.file and .jmx.access.file ) + #ACTIVEMQ_SUNJMX_CONTROL="--jmxurl service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi --jmxuser controlRole --jmxpassword abcd1234" + ACTIVEMQ_SUNJMX_CONTROL="" + + # Specify the queue manager URL for using "browse" option of sysv initscript + if [ -z "$ACTIVEMQ_QUEUEMANAGERURL" ]; then + ACTIVEMQ_QUEUEMANAGERURL="--amqurl tcp://localhost:61616" + fi + + # Set additional JSE arguments + if [ -z "$ACTIVEMQ_SSL_OPTS" ] ; then + #ACTIVEMQ_SSL_OPTS="-Djava.security.properties=$ACTIVEMQ_CONF/java.security" + ACTIVEMQ_SSL_OPTS="" + fi + + # Uncomment to enable remote debugging + #ACTIVEMQ_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" + + # ActiveMQ tries to shutdown the broker by jmx, + # after a specified number of seconds send SIGKILL + if [ -z "$ACTIVEMQ_KILL_MAXSECONDS" ]; then + ACTIVEMQ_KILL_MAXSECONDS=30 + fi + + # Configure a user with non root privileges, if no user is specified do not change user + # (the entire activemq installation should be owned by this user) + ACTIVEMQ_USER="" + + # location of the pidfile + # ACTIVEMQ_PIDFILE="$ACTIVEMQ_DATA/activemq.pid" + + # Location of the java installation + # Specify the location of your java installation using JAVA_HOME, or specify the + # path to the "java" binary using JAVACMD + # (set JAVACMD to "auto" for automatic detection) + #JAVA_HOME="" + JAVACMD="auto" + ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS $ACTIVEMQ_JMX_OPTS -Dhawtio.authenticationEnabled=false -Dhawtio.realm=activemq -Dhawtio.role=admins -Dhawtio.rolePrincipalClasses=org.apache.activemq.jaas.GroupPrincipal" From 5b046cbc282c31afc054d210ffdc30ce04d90564 Mon Sep 17 00:00:00 2001 From: Robert Niedziela <175605712+robsunday@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:24:32 +0200 Subject: [PATCH 19/21] Update jmx-scraper/src/main/resources/activemq.yaml Co-authored-by: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> --- jmx-scraper/src/main/resources/activemq.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jmx-scraper/src/main/resources/activemq.yaml b/jmx-scraper/src/main/resources/activemq.yaml index 044488dfb..6300ab8cb 100644 --- a/jmx-scraper/src/main/resources/activemq.yaml +++ b/jmx-scraper/src/main/resources/activemq.yaml @@ -51,6 +51,8 @@ rules: - bean: org.apache.activemq:type=Broker,brokerName=* metricAttribute: + # minor divergence from activemq.groovy to capture broker name, making it closer to + # the definition in JMX Insights broker: param(brokerName) prefix: activemq. unit: "%" From 36bc5aadb12e70ef7949fa87dca595b47ba6d1db Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Fri, 18 Oct 2024 10:22:56 +0200 Subject: [PATCH 20/21] Code review changes --- .../target_systems/ActiveMqIntegrationTest.java | 2 ++ jmx-scraper/src/main/resources/activemq.yaml | 14 ++++++++++++++ jmx-scraper/src/main/resources/activemq/env | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) 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 1a79469ee..2648ddc60 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 @@ -26,6 +26,8 @@ protected GenericContainer<?> createTargetContainer(int jmxPort) { builder -> builder.from("apache/activemq-classic:5.18.6").build())) .withEnv("LOCAL_JMX", "no") .withCopyFileToContainer( + // Overwrite default ActiveMQ configuration in order to let ActiveMQ use JMX options + // stored in $ACTIVEMQ_JMX_OPTS env variable defined below MountableFile.forClasspathResource("activemq/env"), "/opt/apache-activemq/bin/env") .withEnv( "ACTIVEMQ_JMX_OPTS", diff --git a/jmx-scraper/src/main/resources/activemq.yaml b/jmx-scraper/src/main/resources/activemq.yaml index 6300ab8cb..8387cf5b6 100644 --- a/jmx-scraper/src/main/resources/activemq.yaml +++ b/jmx-scraper/src/main/resources/activemq.yaml @@ -10,11 +10,15 @@ rules: mapping: ProducerCount: metric: producer.count + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "producers" type: updowncounter desc: The number of producers currently attached to the broker. ConsumerCount: metric: consumer.count + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "consumers" type: updowncounter desc: The number of consumers currently reading from the broker. @@ -25,21 +29,29 @@ rules: desc: The percentage of configured memory used. QueueSize: metric: message.current + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "messages" type: updowncounter desc: The current number of messages waiting to be consumed. ExpiredCount: metric: message.expired + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "messages" type: counter desc: The total number of messages not delivered because they expired. EnqueueCount: metric: message.enqueued + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "messages" type: counter desc: The total number of messages received by the broker. DequeueCount: metric: message.dequeued + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "messages" type: counter desc: The total number of messages delivered to consumers. @@ -61,6 +73,8 @@ rules: CurrentConnectionsCount: metric: connection.count type: updowncounter + # Unit name inherited from activemq.groovy file. + # Will be updated to {} semconv notation when we switch to use original files from JMX Insights unit: "connections" desc: The total number of current connections. StorePercentUsage: diff --git a/jmx-scraper/src/main/resources/activemq/env b/jmx-scraper/src/main/resources/activemq/env index bfcc87c6f..23d70bf4f 100644 --- a/jmx-scraper/src/main/resources/activemq/env +++ b/jmx-scraper/src/main/resources/activemq/env @@ -114,4 +114,6 @@ # (set JAVACMD to "auto" for automatic detection) #JAVA_HOME="" JAVACMD="auto" - ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS $ACTIVEMQ_JMX_OPTS -Dhawtio.authenticationEnabled=false -Dhawtio.realm=activemq -Dhawtio.role=admins -Dhawtio.rolePrincipalClasses=org.apache.activemq.jaas.GroupPrincipal" + + # Use JMX options defined in environment variable + ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS $ACTIVEMQ_JMX_OPTS" From 3f7d02f725015b48dedab0db05327eaedc66c3e4 Mon Sep 17 00:00:00 2001 From: robsunday <rniedziela@splunk.com> Date: Fri, 18 Oct 2024 11:37:13 +0200 Subject: [PATCH 21/21] Cleanup unnecessary env variables. Removed env file and use JAVA_TOOL_OPTIONS instead to pass JMX options to ActiveMQ --- .../ActiveMqIntegrationTest.java | 8 +- .../target_systems/TomcatIntegrationTest.java | 1 - jmx-scraper/src/main/resources/activemq/env | 119 ------------------ 3 files changed, 1 insertion(+), 127 deletions(-) delete mode 100644 jmx-scraper/src/main/resources/activemq/env 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 2648ddc60..036996f5e 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 @@ -14,7 +14,6 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; -import org.testcontainers.utility.MountableFile; public class ActiveMqIntegrationTest extends TargetSystemIntegrationTest { @@ -24,13 +23,8 @@ protected GenericContainer<?> createTargetContainer(int jmxPort) { new ImageFromDockerfile() .withDockerfileFromBuilder( builder -> builder.from("apache/activemq-classic:5.18.6").build())) - .withEnv("LOCAL_JMX", "no") - .withCopyFileToContainer( - // Overwrite default ActiveMQ configuration in order to let ActiveMQ use JMX options - // stored in $ACTIVEMQ_JMX_OPTS env variable defined below - MountableFile.forClasspathResource("activemq/env"), "/opt/apache-activemq/bin/env") .withEnv( - "ACTIVEMQ_JMX_OPTS", + "JAVA_TOOL_OPTIONS", "-Dcom.sun.management.jmxremote.port=" + jmxPort + " -Dcom.sun.management.jmxremote.rmi.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 500e89f82..76bacf265 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,7 +30,6 @@ 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("LOCAL_JMX", "no") .withEnv( "CATALINA_OPTS", "-Dcom.sun.management.jmxremote.local.only=false" diff --git a/jmx-scraper/src/main/resources/activemq/env b/jmx-scraper/src/main/resources/activemq/env deleted file mode 100644 index 23d70bf4f..000000000 --- a/jmx-scraper/src/main/resources/activemq/env +++ /dev/null @@ -1,119 +0,0 @@ - #!/bin/sh - # ------------------------------------------------------------------------ - # Licensed to the Apache Software Foundation (ASF) under one or more - # contributor license agreements. See the NOTICE file distributed with - # this work for additional information regarding copyright ownership. - # The ASF 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. - # ------------------------------------------------------------------------ - # - # Configuration file for running Apache Active MQ as standalone provider. - # - # This file overwrites the predefined settings of the sysv init-script. - # You can also use alternate location for default settings - - # invoke the init-script without a argument an review help section "Configuration of this script" - # /etc/default/activemq <activemq user home>/.activemqrc <activemq installation dir>/bin/env - - # Active MQ installation dirs - # ACTIVEMQ_HOME="<Installationdir>/" - # ACTIVEMQ_BASE="$ACTIVEMQ_HOME" - # ACTIVEMQ_CONF="$ACTIVEMQ_BASE/conf" - # ACTIVEMQ_DATA="$ACTIVEMQ_BASE/data" - # ACTIVEMQ_TMP="$ACTIVEMQ_BASE/tmp" - - # Set jvm memory configuration (minimal/maximum amount of memory) - ACTIVEMQ_OPTS_MEMORY="-Xms64M -Xmx256M" - - if [ -z "$ACTIVEMQ_OPTS" ] ; then - ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS_MEMORY -Djava.util.logging.config.file=logging.properties -Djava.security.auth.login.config=$ACTIVEMQ_CONF/login.config" - fi - - if [ -z "$ACTIVEMQ_OUT" ]; then - ACTIVEMQ_OUT="/dev/null" - fi - - # Uncomment to enable audit logging - #ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS -Dorg.apache.activemq.audit=true" - - # Set jvm jmx configuration - # This enables jmx access over a configured jmx-tcp-port. - # You have to configure the first four settings if you run a ibm jvm, caused by the - # fact that IBM's jvm does not support VirtualMachine.attach(PID). - # JMX access is needed for quering a running activemq instance to gain data or to - # trigger management operations. - # - # Example for ${ACTIVEMQ_CONF}/jmx.access: - # --- - # # The "monitorRole" role has readonly access. - # # The "controlRole" role has readwrite access. - # monitorRole readonly - # controlRole readwrite - # --- - # - # Example for ${ACTIVEMQ_CONF}/jmx.password: - # --- - # # The "monitorRole" role has password "abc123". - # # # The "controlRole" role has password "abcd1234". - # monitorRole abc123 - # controlRole abcd1234 - # --- - # - # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.port=11099 " - # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.password.file=${ACTIVEMQ_CONF}/jmx.password" - # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.access.file=${ACTIVEMQ_CONF}/jmx.access" - # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote.ssl=false" - # ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote" - ACTIVEMQ_SUNJMX_START="$ACTIVEMQ_SUNJMX_START -Dcom.sun.management.jmxremote" - - # Set jvm jmx configuration for controlling the broker process - # You only have to configure the first four settings if you run a ibm jvm, caused by the - # fact that IBM's jvm does not support VirtualMachine.attach(PID) - # (see also com.sun.management.jmxremote.port, .jmx.password.file and .jmx.access.file ) - #ACTIVEMQ_SUNJMX_CONTROL="--jmxurl service:jmx:rmi:///jndi/rmi://127.0.0.1:1099/jmxrmi --jmxuser controlRole --jmxpassword abcd1234" - ACTIVEMQ_SUNJMX_CONTROL="" - - # Specify the queue manager URL for using "browse" option of sysv initscript - if [ -z "$ACTIVEMQ_QUEUEMANAGERURL" ]; then - ACTIVEMQ_QUEUEMANAGERURL="--amqurl tcp://localhost:61616" - fi - - # Set additional JSE arguments - if [ -z "$ACTIVEMQ_SSL_OPTS" ] ; then - #ACTIVEMQ_SSL_OPTS="-Djava.security.properties=$ACTIVEMQ_CONF/java.security" - ACTIVEMQ_SSL_OPTS="" - fi - - # Uncomment to enable remote debugging - #ACTIVEMQ_DEBUG_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" - - # ActiveMQ tries to shutdown the broker by jmx, - # after a specified number of seconds send SIGKILL - if [ -z "$ACTIVEMQ_KILL_MAXSECONDS" ]; then - ACTIVEMQ_KILL_MAXSECONDS=30 - fi - - # Configure a user with non root privileges, if no user is specified do not change user - # (the entire activemq installation should be owned by this user) - ACTIVEMQ_USER="" - - # location of the pidfile - # ACTIVEMQ_PIDFILE="$ACTIVEMQ_DATA/activemq.pid" - - # Location of the java installation - # Specify the location of your java installation using JAVA_HOME, or specify the - # path to the "java" binary using JAVACMD - # (set JAVACMD to "auto" for automatic detection) - #JAVA_HOME="" - JAVACMD="auto" - - # Use JMX options defined in environment variable - ACTIVEMQ_OPTS="$ACTIVEMQ_OPTS $ACTIVEMQ_JMX_OPTS"