Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add wildfly.yaml to JMX scraper #1531

Merged
merged 26 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f760a1
backport few changes from jetty PR
SylvainJuge Nov 4, 2024
f4c4be2
add ability to use login/pwd + extra jars
SylvainJuge Nov 4, 2024
c42b628
add support for login/pwd auth
SylvainJuge Nov 4, 2024
8b5da8d
add wildfly with remote scraper config
SylvainJuge Nov 4, 2024
86fca55
fix inconsistencies + start testing
SylvainJuge Nov 4, 2024
7dbafa8
remove useless changes + reformat
SylvainJuge Nov 4, 2024
20354af
all but session metrics covered
SylvainJuge Nov 5, 2024
927fee2
prepare moving TestApp to separate sourceSet
SylvainJuge Nov 5, 2024
2029c99
move test app to separate sourceset
SylvainJuge Nov 5, 2024
bccf90d
move test apps + deploy on wildfly
SylvainJuge Nov 6, 2024
04ea4bc
add wildfly session metrics + tests
SylvainJuge Nov 6, 2024
9684fae
Merge branch 'main' of github.com:open-telemetry/opentelemetry-java-c…
SylvainJuge Nov 6, 2024
3a53a2f
fix api change
SylvainJuge Nov 6, 2024
7fca8ca
fix test app for java8 compat
SylvainJuge Nov 6, 2024
171b141
attempt to make activemq startup detect better
SylvainJuge Nov 6, 2024
5abd3c8
bootstrap documentation
SylvainJuge Nov 7, 2024
83ad212
lint markdown
SylvainJuge Nov 7, 2024
3ab2f28
detect container startup with open ports
SylvainJuge Nov 12, 2024
50063db
use constants for ports
SylvainJuge Nov 14, 2024
4c25755
avoid manual temp file management
SylvainJuge Nov 14, 2024
250e3ed
use 'By' in place of 'by' for units
SylvainJuge Nov 14, 2024
d286622
remove duplication in wildfly yaml
SylvainJuge Nov 14, 2024
6645d4b
fix test change for units
SylvainJuge Nov 14, 2024
1a858bb
enhance readme
SylvainJuge Nov 14, 2024
6e70488
reformat
SylvainJuge Nov 14, 2024
e15e4c8
reformat again
SylvainJuge Nov 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -92,12 +92,14 @@ void endToEnd() {
entry("server", "default-server"),
entry("listener", "default"),
entry("state", "out"))),

// TODO from here:
metric ->
assertSumWithAttributes(
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 +109,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
24 changes: 14 additions & 10 deletions jmx-metrics/src/main/resources/target-systems/wildfly.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,31 @@
*/

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",
Expand All @@ -44,17 +48,17 @@ otel.instrument(beanWildflyHttpListener, "wildfly.network.io", "The number of by
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)
38 changes: 38 additions & 0 deletions jmx-scraper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,44 @@ 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.target.system=...`
- system properties `java -jar scraper.jar`
- java properties file: `java -jar config.properties`
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -25,11 +25,12 @@ 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());
.waitingFor(Wait.forLogMessage(".*Apache ActiveMQ.*started.*", 1));
SylvainJuge marked this conversation as resolved.
Show resolved Hide resolved
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ protected GenericContainer<?> createTargetContainer(int jmxPort) {
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ protected GenericContainer<?> createTargetContainer(int jmxPort) {
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ protected GenericContainer<?> createTargetContainer(int jmxPort) {
}

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

Expand Down
Loading
Loading