Skip to content

Commit

Permalink
add wildfly.yaml to JMX scraper (#1531)
Browse files Browse the repository at this point in the history
  • Loading branch information
SylvainJuge authored Nov 14, 2024
1 parent 6ea0fa4 commit 74a3d78
Show file tree
Hide file tree
Showing 27 changed files with 609 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ void endToEnd() {
metric,
"wildfly.request.count",
"The number of requests received.",
"{requests}",
"{request}",
attrs ->
attrs.containsOnly(
entry("server", "default-server"), entry("listener", "default"))),
Expand All @@ -72,7 +72,7 @@ void endToEnd() {
metric,
"wildfly.request.server_error",
"The number of requests that have resulted in a 5xx response.",
"{requests}",
"{request}",
attrs ->
attrs.containsOnly(
entry("server", "default-server"), entry("listener", "default"))),
Expand All @@ -81,7 +81,7 @@ void endToEnd() {
metric,
"wildfly.network.io",
"The number of bytes transmitted.",
"by",
"By",
attrs ->
attrs.containsOnly(
entry("server", "default-server"),
Expand All @@ -97,7 +97,7 @@ void endToEnd() {
metric,
"wildfly.jdbc.connection.open",
"The number of open jdbc connections.",
"{connections}",
"{connection}",
attrs ->
attrs.containsOnly(entry("data_source", "ExampleDS"), entry("state", "active")),
attrs ->
Expand All @@ -107,20 +107,20 @@ void endToEnd() {
metric,
"wildfly.jdbc.request.wait",
"The number of jdbc connections that had to wait before opening.",
"{requests}",
"{request}",
attrs -> attrs.containsOnly(entry("data_source", "ExampleDS"))),
metric ->
assertSum(
metric,
"wildfly.jdbc.transaction.count",
"The number of transactions created.",
"{transactions}"),
"{transaction}"),
metric ->
assertSumWithAttributes(
metric,
"wildfly.jdbc.rollback.count",
"The number of transactions rolled back.",
"{transactions}",
"{transaction}",
attrs -> attrs.containsOnly(entry("cause", "system")),
attrs -> attrs.containsOnly(entry("cause", "resource")),
attrs -> attrs.containsOnly(entry("cause", "application"))));
Expand Down
26 changes: 15 additions & 11 deletions jmx-metrics/src/main/resources/target-systems/wildfly.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,46 +15,50 @@
*/

def beanWildflyDeployment = otel.mbeans("jboss.as:deployment=*,subsystem=undertow")
otel.instrument(beanWildflyDeployment, "wildfly.session.count", "The number of sessions created.", "{sessions}",
// no test covers sessions
otel.instrument(beanWildflyDeployment, "wildfly.session.count", "The number of sessions created.", "{session}",
["deployment": { mbean -> mbean.name().getKeyProperty("deployment")}],
"sessionsCreated", otel.&longCounterCallback)
otel.instrument(beanWildflyDeployment, "wildfly.session.active", "The number of currently active sessions.", "{sessions}",

otel.instrument(beanWildflyDeployment, "wildfly.session.active", "The number of currently active sessions.", "{session}",
["deployment": { mbean -> mbean.name().getKeyProperty("deployment")}],
"activeSessions", otel.&longUpDownCounterCallback)
otel.instrument(beanWildflyDeployment, "wildfly.session.expired", "The number of sessions that have expired.", "{sessions}",
otel.instrument(beanWildflyDeployment, "wildfly.session.expired", "The number of sessions that have expired.", "{session}",
["deployment": { mbean -> mbean.name().getKeyProperty("deployment")}],
"expiredSessions", otel.&longCounterCallback)
otel.instrument(beanWildflyDeployment, "wildfly.session.rejected", "The number of sessions that have been rejected.", "{sessions}",
otel.instrument(beanWildflyDeployment, "wildfly.session.rejected", "The number of sessions that have been rejected.", "{session}",
["deployment": { mbean -> mbean.name().getKeyProperty("deployment")}],
"rejectedSessions", otel.&longCounterCallback)



def beanWildflyHttpListener = otel.mbeans("jboss.as:subsystem=undertow,server=*,http-listener=*")
otel.instrument(beanWildflyHttpListener, "wildfly.request.count", "The number of requests received.", "{requests}",
otel.instrument(beanWildflyHttpListener, "wildfly.request.count", "The number of requests received.", "{request}",
["server": { mbean -> mbean.name().getKeyProperty("server")}, "listener": { mbean -> mbean.name().getKeyProperty("http-listener")}],
"requestCount", otel.&longCounterCallback)
otel.instrument(beanWildflyHttpListener, "wildfly.request.time", "The total amount of time spent on requests.", "ns",
["server": { mbean -> mbean.name().getKeyProperty("server")}, "listener": { mbean -> mbean.name().getKeyProperty("http-listener")}],
"processingTime", otel.&longCounterCallback)
otel.instrument(beanWildflyHttpListener, "wildfly.request.server_error", "The number of requests that have resulted in a 5xx response.", "{requests}",
otel.instrument(beanWildflyHttpListener, "wildfly.request.server_error", "The number of requests that have resulted in a 5xx response.", "{request}",
["server": { mbean -> mbean.name().getKeyProperty("server")}, "listener": { mbean -> mbean.name().getKeyProperty("http-listener")}],
"errorCount", otel.&longCounterCallback)
otel.instrument(beanWildflyHttpListener, "wildfly.network.io", "The number of bytes transmitted.", "by",
otel.instrument(beanWildflyHttpListener, "wildfly.network.io", "The number of bytes transmitted.", "By",
["server": { mbean -> mbean.name().getKeyProperty("server")}, "listener": { mbean -> mbean.name().getKeyProperty("http-listener")}],
["bytesSent":["state":{"out"}], "bytesReceived":["state":{"in"}]],
otel.&longCounterCallback)

def beanWildflyDataSource = otel.mbeans("jboss.as:subsystem=datasources,data-source=*,statistics=pool")
otel.instrument(beanWildflyDataSource, "wildfly.jdbc.connection.open", "The number of open jdbc connections.", "{connections}",
otel.instrument(beanWildflyDataSource, "wildfly.jdbc.connection.open", "The number of open jdbc connections.", "{connection}",
["data_source": { mbean -> mbean.name().getKeyProperty("data-source")}],
["ActiveCount":["state":{"active"}], "IdleCount":["state":{"idle"}]],
otel.&longUpDownCounterCallback)
otel.instrument(beanWildflyDataSource, "wildfly.jdbc.request.wait", "The number of jdbc connections that had to wait before opening.", "{requests}",
otel.instrument(beanWildflyDataSource, "wildfly.jdbc.request.wait", "The number of jdbc connections that had to wait before opening.", "{request}",
["data_source": { mbean -> mbean.name().getKeyProperty("data-source")}],
"WaitCount", otel.&longCounterCallback)

def beanWildflyTransaction = otel.mbean("jboss.as:subsystem=transactions")
otel.instrument(beanWildflyTransaction, "wildfly.jdbc.transaction.count", "The number of transactions created.", "{transactions}",
otel.instrument(beanWildflyTransaction, "wildfly.jdbc.transaction.count", "The number of transactions created.", "{transaction}",
"numberOfTransactions", otel.&longCounterCallback)
otel.instrument(beanWildflyTransaction, "wildfly.jdbc.rollback.count", "The number of transactions rolled back.", "{transactions}",
otel.instrument(beanWildflyTransaction, "wildfly.jdbc.rollback.count", "The number of transactions rolled back.", "{transaction}",
["numberOfSystemRollbacks":["cause":{"system"}], "numberOfResourceRollbacks":["cause":{"resource"}], "numberOfApplicationRollbacks":["cause":{"application"}]],
otel.&longCounterCallback)
42 changes: 42 additions & 0 deletions jmx-scraper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,48 @@ The JMX MBeans and their metric mappings are defined in YAML and reuse implement
This is currently a work-in-progress component not ready to be used in production.
The end goal is to provide an alternative to the [JMX Gatherer](../jmx-metrics/README.md) utility.

## Usage

The general command to invoke JMX scraper is `java -jar scraper.jar <config>`, where `scraper.jar`
is the `build/libs/opentelemetry-jmx-scraper-<version>.jar` packaged binary when building this module.

Minimal configuration required

- `otel.jmx.service.url` for example `service:jmx:rmi:///jndi/rmi://server:9999/jmxrmi` for `server`
host on port `9999` with RMI JMX connector.
- `otel.jmx.target.system` or `otel.jmx.custom.scraping.config`

Configuration can be provided through:

- command line arguments:
`java -jar scraper.jar --config otel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi otel.jmx.target.system=tomcat`.
- command line arguments JVM system properties:
`java -Dotel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi -Dotel.jmx.target.system=tomcat -jar scraper.jar`.
- java properties file: `java -jar scraper.jar -config config.properties`.
- stdin: `java -jar scraper.jar -config -` where `otel.jmx.target.system=tomcat` and
`otel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi` is written to stdin.

TODO: update this once autoconfiguration is supported

### Configuration reference

TODO

### Extra libraries in classpath

By default, only the RMI JMX connector is provided by the JVM, so it might be required to add extra
libraries in the classpath when connecting to remote JVMs that are not directly accessible with RMI.

One known example of this is the Wildfly/Jboss HTTP management interface for which the `jboss-client.jar`
needs to be used to support `otel.jmx.service.url` = `service:jmx:remote+http://server:9999`.

When doing so, the `java -jar` command can´t be used, we have to provide the classpath with
`-cp`/`--class-path`/`-classpath` option and provide the main class file name:

```
java -cp scraper.jar:jboss-client.jar io.opentelemetry.contrib.jmxscraper.JmxScraper <config>
```

## Component owners

- [Jason Plumb](https://github.com/breedx-splk), Splunk
Expand Down
19 changes: 9 additions & 10 deletions jmx-scraper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,16 @@ tasks {

withType<Test>().configureEach {
dependsOn(shadowJar)
dependsOn(named("appJar"))
systemProperty("shadow.jar.path", shadowJar.get().archiveFile.get().asFile.absolutePath)
systemProperty("app.jar.path", named<Jar>("appJar").get().archiveFile.get().asFile.absolutePath)

val testAppTask = project("test-app").tasks.named<Jar>("jar")
dependsOn(testAppTask)
systemProperty("app.jar.path", testAppTask.get().archiveFile.get().asFile.absolutePath)

val testWarTask = project("test-webapp").tasks.named<Jar>("war")
dependsOn(testWarTask)
systemProperty("app.war.path", testWarTask.get().archiveFile.get().asFile.absolutePath)

systemProperty("gradle.project.version", "${project.version}")
}

Expand All @@ -74,14 +81,6 @@ tasks {
}
}

tasks.register<Jar>("appJar") {
from(sourceSets.get("integrationTest").output)
archiveClassifier.set("app")
manifest {
attributes["Main-Class"] = "io.opentelemetry.contrib.jmxscraper.TestApp"
}
}

// Don't publish non-shadowed jar (shadowJar is in shadowRuntimeElements)
with(components["java"] as AdhocComponentWithVariants) {
configurations.forEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ void loginPwdAuth() {
testConnector(
() ->
JmxConnectorBuilder.createNew(app.getHost(), app.getMappedPort(port))
.userCredentials(login, pwd)
.withUser(login)
.withPassword(pwd)
.build());
}
}
Expand All @@ -75,7 +76,7 @@ private static void testConnector(ConnectorSupplier connectorSupplier) {
.satisfies(
connection -> {
try {
ObjectName name = new ObjectName(TestApp.OBJECT_NAME);
ObjectName name = new ObjectName("io.opentelemetry.test:name=TestApp");
Object value = connection.getAttribute(name, "IntValue");
assertThat(value).isEqualTo(42);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ public class JmxScraperContainer extends GenericContainer<JmxScraperContainer> {
private String serviceUrl;
private int intervalMillis;
private final Set<String> customYamlFiles;
private String user;
private String password;
private final List<String> extraJars;

public JmxScraperContainer(String otlpEndpoint) {
super("openjdk:8u342-jre-slim");
public JmxScraperContainer(String otlpEndpoint, String baseImage) {
super(baseImage);

String scraperJarPath = System.getProperty("shadow.jar.path");
assertThat(scraperJarPath).isNotNull();
Expand All @@ -42,6 +45,7 @@ public JmxScraperContainer(String otlpEndpoint) {
this.targetSystems = new HashSet<>();
this.customYamlFiles = new HashSet<>();
this.intervalMillis = 1000;
this.extraJars = new ArrayList<>();
}

@CanIgnoreReturnValue
Expand All @@ -57,11 +61,52 @@ public JmxScraperContainer withIntervalMillis(int intervalMillis) {
}

@CanIgnoreReturnValue
public JmxScraperContainer withService(String host, int port) {
public JmxScraperContainer withRmiServiceUrl(String host, int port) {
// TODO: adding a way to provide 'host:port' syntax would make this easier for end users
this.serviceUrl =
return withServiceUrl(
String.format(
Locale.getDefault(), "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", host, port);
Locale.getDefault(), "service:jmx:rmi:///jndi/rmi://%s:%d/jmxrmi", host, port));
}

@CanIgnoreReturnValue
public JmxScraperContainer withServiceUrl(String serviceUrl) {
this.serviceUrl = serviceUrl;
return this;
}

/**
* Sets JMX user login
*
* @param user user login
* @return this
*/
@CanIgnoreReturnValue
public JmxScraperContainer withUser(String user) {
this.user = user;
return this;
}

/**
* Sets JMX password
*
* @param password user password
* @return this
*/
@CanIgnoreReturnValue
public JmxScraperContainer withPassword(String password) {
this.password = password;
return this;
}

/**
* Adds path to an extra jar for classpath
*
* @param jarPath path to an extra jar that should be added to jmx scraper classpath
* @return this
*/
@CanIgnoreReturnValue
public JmxScraperContainer withExtraJar(String jarPath) {
this.extraJars.add(jarPath);
return this;
}

Expand Down Expand Up @@ -89,15 +134,30 @@ public void start() {
arguments.add("-Dotel.jmx.service.url=" + serviceUrl);
arguments.add("-Dotel.jmx.interval.milliseconds=" + intervalMillis);

if (user != null) {
arguments.add("-Dotel.jmx.username=" + user);
}
if (password != null) {
arguments.add("-Dotel.jmx.password=" + password);
}

if (!customYamlFiles.isEmpty()) {
for (String yaml : customYamlFiles) {
this.withCopyFileToContainer(MountableFile.forClasspathResource(yaml), yaml);
}
arguments.add("-Dotel.jmx.config=" + String.join(",", customYamlFiles));
}

arguments.add("-jar");
arguments.add("/scraper.jar");
if (extraJars.isEmpty()) {
// using "java -jar" to start
arguments.add("-jar");
arguments.add("/scraper.jar");
} else {
// using "java -cp" to start
arguments.add("-cp");
arguments.add("/scraper.jar:" + String.join(":", extraJars));
arguments.add("io.opentelemetry.contrib.jmxscraper.JmxScraper");
}

this.withCommand(arguments.toArray(new String[0]));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import org.testcontainers.shaded.com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.testcontainers.utility.MountableFile;

/** Test container that allows to execute {@link TestApp} in an isolated container */
/** Test container that allows to execute TestApp in an isolated container */
public class TestAppContainer extends GenericContainer<TestAppContainer> {

private final Map<String, String> properties;
Expand All @@ -38,8 +38,7 @@ public TestAppContainer() {

this.withCopyFileToContainer(MountableFile.forHostPath(appJar), "/app.jar")
.waitingFor(
Wait.forLogMessage(TestApp.APP_STARTED_MSG + "\\n", 1)
.withStartupTimeout(Duration.ofSeconds(5)))
Wait.forLogMessage("app started\\n", 1).withStartupTimeout(Duration.ofSeconds(5)))
.withCommand("java", "-jar", "/app.jar");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
import static org.assertj.core.api.Assertions.entry;

import io.opentelemetry.contrib.jmxscraper.JmxScraperContainer;
import java.nio.file.Path;
import java.time.Duration;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.images.builder.ImageFromDockerfile;

public class ActiveMqIntegrationTest extends TargetSystemIntegrationTest {

private static final int ACTIVEMQ_PORT = 61616;

@Override
protected GenericContainer<?> createTargetContainer(int jmxPort) {
return new GenericContainer<>(
Expand All @@ -25,11 +28,13 @@ protected GenericContainer<?> createTargetContainer(int jmxPort) {
builder -> builder.from("apache/activemq-classic:5.18.6").build()))
.withEnv("JAVA_TOOL_OPTIONS", genericJmxJvmArguments(jmxPort))
.withStartupTimeout(Duration.ofMinutes(2))
.waitingFor(Wait.forListeningPort());
.withExposedPorts(ACTIVEMQ_PORT, jmxPort)
.waitingFor(Wait.forListeningPorts(ACTIVEMQ_PORT, jmxPort));
}

@Override
protected JmxScraperContainer customizeScraperContainer(JmxScraperContainer scraper) {
protected JmxScraperContainer customizeScraperContainer(
JmxScraperContainer scraper, GenericContainer<?> target, Path tempDir) {
return scraper.withTargetSystem("activemq");
}

Expand Down
Loading

0 comments on commit 74a3d78

Please sign in to comment.