diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index bab7b7cb7..8c2a84675 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -13,19 +13,32 @@ 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") 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") 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 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 metrics) { - // TODO: Verify gathered metrics + protected void verifyMetrics() { + // those values depend on the JVM GC configured + List gcLabels = + Arrays.asList( + "Code Cache", + "PS Eden Space", + "PS Old Gen", + "Metaspace", + "Compressed Class Space", + "PS Survivor Space"); + List 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 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 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>... 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>... 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 points, List types) { + Consumer>[] assertions = + types.stream() + .map( + type -> + (Consumer>) + attrs -> attrs.containsOnly(entry("name", type))) + .toArray(Consumer[]::new); + + assertAttributedPoints(points, assertions); + } + + @SuppressWarnings("unchecked") + private static void assertAttributedPoints( + List points, + Consumer>... attributeGroupAssertions) { + Consumer>[] assertions = + Arrays.stream(attributeGroupAssertions) + .map(assertion -> (Consumer>) 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 2cce282f9..510b4eab5 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,6 +5,9 @@ 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; @@ -13,11 +16,18 @@ 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 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 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; @@ -30,6 +40,7 @@ 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"; @@ -105,10 +116,45 @@ void endToEndTest() { scraper = customizeScraperContainer(scraper); scraper.start(); - verifyMetrics(otlpServer.getMetrics()); + verifyMetrics(); + } + + protected void waitAndAssertMetrics(Iterable> assertions) { + await() + .atMost(Duration.ofSeconds(30)) + .untilAsserted( + () -> { + List receivedMetrics = otlpServer.getMetrics(); + assertThat(receivedMetrics).describedAs("no metric received").isNotEmpty(); + + List 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 assertion : assertions) { + assertThat(metrics).anySatisfy(assertion); + } + }); } - protected abstract void verifyMetrics(List metrics); + @SafeVarargs + @SuppressWarnings("varargs") + protected final void waitAndAssertMetrics(Consumer... assertions) { + waitAndAssertMetrics(Arrays.asList(assertions)); + } + + protected abstract void verifyMetrics(); protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scraper) { return scraper; @@ -137,6 +183,10 @@ protected void configure(ServerBuilder sb) { public void export( ExportMetricsServiceRequest request, StreamObserver responseObserver) { + + // verbose but helpful to diagnose what is received + logger.debug("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 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 ebed7c780..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 @@ -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,25 @@ public class JmxScraper { */ @SuppressWarnings({"SystemOut", "SystemExitOutsideMain"}) 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"); + } + 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) { @@ -58,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); } } @@ -75,10 +97,10 @@ static Properties parseArgs(List 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); @@ -109,29 +131,59 @@ 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) { + // silently 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)); - - logger.info("JMX scraping started"); + private static MetricConfiguration getMetricConfig(JmxScraperConfig scraperConfig) { + MetricConfiguration config = new MetricConfiguration(); + for (String system : scraperConfig.getTargetSystems()) { + addRulesForSystem(system, config); + } + // TODO : add ability for user to provide custom yaml configurations + 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 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/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 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 = ""; 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 new file mode 100644 index 000000000..076be6400 --- /dev/null +++ b/jmx-scraper/src/main/resources/tomcat.yaml @@ -0,0 +1,83 @@ +--- + +# 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: tomcat.sessions + type: gauge + unit: sessions + desc: The number of active sessions + + - beans: + - Catalina:type=GlobalRequestProcessor,name=* + - Tomcat:type=GlobalRequestProcessor,name=* + prefix: tomcat. + metricAttribute: + proto_handler: param(name) + mapping: + errorCount: + metric: errors + type: counter + unit: errors + desc: The number of errors encountered + requestCount: + metric: request_count + type: counter + unit: requests + desc: The total requests + maxTime: + metric: max_time + type: gauge + unit: ms + desc: Maximum time to process a request + processingTime: + metric: processing_time + type: counter + unit: ms + desc: The total processing time + bytesSent: + metric: traffic + type: counter + unit: by + desc: The number of bytes transmitted and received + metricAttribute: + direction: const(sent) + bytesReceived: + metric: traffic + type: counter + unit: by + desc: The number of bytes transmitted and received + metricAttribute: + direction: const(received) + + - beans: + - Catalina:type=ThreadPool,name=* + - Tomcat:type=ThreadPool,name=* + prefix: tomcat. + metricAttribute: + proto_handler: param(name) + mapping: + currentThreadCount: + metric: threads + desc: The number of threads + type: gauge + unit: threads + metricAttribute: + state: const(idle) + currentThreadsBusy: + metric: threads + desc: The number of threads + type: gauge + unit: threads + metricAttribute: + state: const(busy)