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"