Skip to content

Commit 3db20f7

Browse files
authored
jmx scraper with sdk autoconfig (#1651)
1 parent ca282f2 commit 3db20f7

16 files changed

+581
-491
lines changed

jmx-scraper/README.md

+56-6
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,70 @@ Minimal configuration required
2121
Configuration can be provided through:
2222

2323
- command line arguments:
24-
`java -jar scraper.jar --config otel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi otel.jmx.target.system=tomcat`.
24+
`java -jar scraper.jar -config otel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi otel.jmx.target.system=tomcat`.
2525
- command line arguments JVM system properties:
2626
`java -Dotel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi -Dotel.jmx.target.system=tomcat -jar scraper.jar`.
2727
- java properties file: `java -jar scraper.jar -config config.properties`.
2828
- stdin: `java -jar scraper.jar -config -` where `otel.jmx.target.system=tomcat` and
2929
`otel.jmx.service.url=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi` is written to stdin.
30+
- environment variables: `OTEL_JMX_TARGET_SYSTEM=tomcat OTEL_JMX_SERVICE_URL=service:jmx:rmi:///jndi/rmi://tomcat:9010/jmxrmi java -jar scraper.jar`
3031

31-
TODO: update this once autoconfiguration is supported
32+
SDK auto-configuration is being used, so all the configuration options can be set using the java
33+
properties syntax or the corresponding environment variables.
3234

33-
### Configuration reference
35+
For example the `otel.jmx.service.url` option can be set with the `OTEL_JMX_SERVICE_URL` environment variable.
3436

35-
TODO
37+
## Configuration reference
3638

37-
### Extra libraries in classpath
39+
| config option | description |
40+
|-----------------------------------|----------------------------------------------------------------------------------------------|
41+
| `otel.jmx.service.url` | mandatory JMX URL to connect to the remote JVM |
42+
| `otel.jmx.target.system` | comma-separated list of systems to monitor, mandatory unless a custom configuration is used |
43+
| `otel.jmx.custom.scraping.config` | path to a custom YAML metrics definition, mandatory when `otel.jmx.target.system` is not set |
44+
| `otel.jmx.username` | user name for JMX connection, mandatory when JMX authentication is enabled on target JVM |
45+
| `otel.jmx.password` | password for JMX connection, mandatory when JMX authentication is enabled on target JVM |
46+
47+
Supported values for `otel.jmx.target.system`:
48+
49+
| `otel.jmx.target.system` | description |
50+
|--------------------------|-----------------------|
51+
| `activemq` | Apache ActiveMQ |
52+
| `cassandra` | Apache Cassandra |
53+
| `hbase` | Apache HBase |
54+
| `hadoop` | Apache Hadoop |
55+
| `jetty` | Eclipse Jetty |
56+
| `jvm` | JVM runtime metrics |
57+
| `kafka` | Apache Kafka |
58+
| `kafka-consumer` | Apache Kafka consumer |
59+
| `kafka-producer` | Apache Kafka producer |
60+
| `solr` | Apache Solr |
61+
| `tomcat` | Apache Tomcat |
62+
| `wildfly` | Wildfly |
63+
64+
The following SDK configuration options are also relevant
65+
66+
| config option | default value | description |
67+
|-------------------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
68+
| `otel.metric.export.interval` | `1m` (1 minute) | metric export interval, also controls the JMX sampling interval |
69+
| `otel.metrics.exporter` | `otlp` | comma-separated list of metrics exporters supported values are `otlp` and `logging`, additional values might be provided through extra libraries in the classpath |
70+
71+
In addition to OpenTelemetry configuration, the following Java system properties can be provided
72+
through the command-line arguments, properties file or stdin and will be propagated to the JVM system properties:
73+
74+
- `javax.net.ssl.keyStore`
75+
- `javax.net.ssl.keyStorePassword`
76+
- `javax.net.ssl.trustStore`
77+
- `javax.net.ssl.trustStorePassword`
78+
79+
Those JVM system properties can't be set through individual environment variables, but they can still
80+
be set through the standard `JAVA_TOOL_OPTIONS` environment variable using the `-D` prefix.
81+
82+
## Troubleshooting
83+
84+
In order to investigate when and what metrics are being captured and sent, setting the `otel.metrics.exporter`
85+
configuration option to include `logging` exporter provides log messages when metrics are being exported.
86+
87+
## Extra libraries in classpath
3888

3989
By default, only the RMI JMX connector is provided by the JVM, so it might be required to add extra
4090
libraries in the classpath when connecting to remote JVMs that are not directly accessible with RMI.
@@ -45,7 +95,7 @@ needs to be used to support `otel.jmx.service.url` = `service:jmx:remote+http://
4595
When doing so, the `java -jar` command can´t be used, we have to provide the classpath with
4696
`-cp`/`--class-path`/`-classpath` option and provide the main class file name:
4797

48-
```
98+
```bash
4999
java -cp scraper.jar:jboss-client.jar io.opentelemetry.contrib.jmxscraper.JmxScraper <config>
50100
```
51101

jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java

+2-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ public class JmxScraperContainer extends GenericContainer<JmxScraperContainer> {
2424
private final String endpoint;
2525
private final Set<String> targetSystems;
2626
private String serviceUrl;
27-
private int intervalMillis;
2827
private final Set<String> customYamlFiles;
2928
private String user;
3029
private String password;
@@ -44,7 +43,6 @@ public JmxScraperContainer(String otlpEndpoint, String baseImage) {
4443
this.endpoint = otlpEndpoint;
4544
this.targetSystems = new HashSet<>();
4645
this.customYamlFiles = new HashSet<>();
47-
this.intervalMillis = 1000;
4846
this.extraJars = new ArrayList<>();
4947
}
5048

@@ -54,12 +52,6 @@ public JmxScraperContainer withTargetSystem(String targetSystem) {
5452
return this;
5553
}
5654

57-
@CanIgnoreReturnValue
58-
public JmxScraperContainer withIntervalMillis(int intervalMillis) {
59-
this.intervalMillis = intervalMillis;
60-
return this;
61-
}
62-
6355
@CanIgnoreReturnValue
6456
public JmxScraperContainer withRmiServiceUrl(String host, int port) {
6557
// TODO: adding a way to provide 'host:port' syntax would make this easier for end users
@@ -132,7 +124,8 @@ public void start() {
132124
throw new IllegalStateException("Missing service URL");
133125
}
134126
arguments.add("-Dotel.jmx.service.url=" + serviceUrl);
135-
arguments.add("-Dotel.jmx.interval.milliseconds=" + intervalMillis);
127+
// always use a very short export interval for testing
128+
arguments.add("-Dotel.metric.export.interval=1s");
136129

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

jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/ArgumentsParsingException.java

-14
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.jmxscraper;
7+
8+
/**
9+
* Exception indicating something is wrong with the provided arguments or reading the configuration
10+
* from them
11+
*/
12+
public class InvalidArgumentException extends Exception {
13+
14+
private static final long serialVersionUID = 0L;
15+
16+
public InvalidArgumentException(String msg) {
17+
super(msg);
18+
}
19+
20+
public InvalidArgumentException(String msg, Throwable cause) {
21+
super(msg, cause);
22+
}
23+
}

jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java

+62-37
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
package io.opentelemetry.contrib.jmxscraper;
77

88
import io.opentelemetry.api.GlobalOpenTelemetry;
9-
import io.opentelemetry.contrib.jmxscraper.config.ConfigurationException;
109
import io.opentelemetry.contrib.jmxscraper.config.JmxScraperConfig;
10+
import io.opentelemetry.contrib.jmxscraper.config.PropertiesCustomizer;
11+
import io.opentelemetry.contrib.jmxscraper.config.PropertiesSupplier;
1112
import io.opentelemetry.instrumentation.jmx.engine.JmxMetricInsight;
1213
import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration;
1314
import io.opentelemetry.instrumentation.jmx.yaml.RuleParser;
15+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
16+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
1417
import java.io.DataInputStream;
1518
import java.io.IOException;
1619
import java.io.InputStream;
@@ -19,9 +22,11 @@
1922
import java.util.Arrays;
2023
import java.util.Collections;
2124
import java.util.List;
25+
import java.util.Map;
2226
import java.util.Optional;
2327
import java.util.Properties;
2428
import java.util.concurrent.atomic.AtomicBoolean;
29+
import java.util.logging.Level;
2530
import java.util.logging.Logger;
2631
import javax.management.MBeanServerConnection;
2732
import javax.management.remote.JMXConnector;
@@ -30,8 +35,6 @@ public class JmxScraper {
3035
private static final Logger logger = Logger.getLogger(JmxScraper.class.getName());
3136
private static final String CONFIG_ARG = "-config";
3237

33-
private static final String OTEL_AUTOCONFIGURE = "otel.java.global-autoconfigure.enabled";
34-
3538
private final JmxConnectorBuilder client;
3639
private final JmxMetricInsight service;
3740
private final JmxScraperConfig config;
@@ -43,69 +46,88 @@ public class JmxScraper {
4346
*
4447
* @param args - must be of the form "-config {jmx_config_path,'-'}"
4548
*/
46-
@SuppressWarnings({"SystemOut", "SystemExitOutsideMain"})
49+
@SuppressWarnings("SystemExitOutsideMain")
4750
public static void main(String[] args) {
4851

49-
// enable SDK auto-configure if not explicitly set by user
50-
// TODO: refactor this to use AutoConfiguredOpenTelemetrySdk
51-
if (System.getProperty(OTEL_AUTOCONFIGURE) == null) {
52-
System.setProperty(OTEL_AUTOCONFIGURE, "true");
53-
}
52+
// set log format
53+
System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT %4$s %5$s%n");
5454

5555
try {
56-
JmxScraperConfig config =
57-
JmxScraperConfig.fromProperties(parseArgs(Arrays.asList(args)), System.getProperties());
58-
// propagate effective user-provided configuration to JVM system properties
59-
// this also enables SDK auto-configuration to use those properties
60-
config.propagateSystemProperties();
56+
Properties argsConfig = parseArgs(Arrays.asList(args));
57+
propagateToSystemProperties(argsConfig);
58+
59+
// auto-configure and register SDK
60+
PropertiesCustomizer configCustomizer = new PropertiesCustomizer();
61+
AutoConfiguredOpenTelemetrySdk.builder()
62+
.addPropertiesSupplier(new PropertiesSupplier(argsConfig))
63+
.addPropertiesCustomizer(configCustomizer)
64+
.setResultAsGlobal()
65+
.build();
66+
67+
JmxScraperConfig scraperConfig = configCustomizer.getScraperConfig();
68+
69+
long exportSeconds = scraperConfig.getSamplingInterval().toMillis() / 1000;
70+
logger.log(Level.INFO, "metrics export interval (seconds) = " + exportSeconds);
6171

6272
JmxMetricInsight service =
6373
JmxMetricInsight.createService(
64-
GlobalOpenTelemetry.get(), config.getIntervalMilliseconds());
65-
JmxConnectorBuilder connectorBuilder = JmxConnectorBuilder.createNew(config.getServiceUrl());
74+
GlobalOpenTelemetry.get(), scraperConfig.getSamplingInterval().toMillis());
75+
JmxConnectorBuilder connectorBuilder =
76+
JmxConnectorBuilder.createNew(scraperConfig.getServiceUrl());
6677

67-
Optional.ofNullable(config.getUsername()).ifPresent(connectorBuilder::withUser);
68-
Optional.ofNullable(config.getPassword()).ifPresent(connectorBuilder::withPassword);
78+
Optional.ofNullable(scraperConfig.getUsername()).ifPresent(connectorBuilder::withUser);
79+
Optional.ofNullable(scraperConfig.getPassword()).ifPresent(connectorBuilder::withPassword);
6980

70-
JmxScraper jmxScraper = new JmxScraper(connectorBuilder, service, config);
81+
JmxScraper jmxScraper = new JmxScraper(connectorBuilder, service, scraperConfig);
7182
jmxScraper.start();
72-
73-
} catch (ArgumentsParsingException e) {
74-
System.err.println("ERROR: " + e.getMessage());
75-
System.err.println(
83+
} catch (ConfigurationException e) {
84+
logger.log(Level.SEVERE, "invalid configuration ", e);
85+
System.exit(1);
86+
} catch (InvalidArgumentException e) {
87+
logger.log(Level.SEVERE, "invalid configuration provided through arguments", e);
88+
logger.info(
7689
"Usage: java -jar <path_to_jmxscraper.jar> "
7790
+ "-config <path_to_config.properties or - for stdin>");
7891
System.exit(1);
79-
} catch (ConfigurationException e) {
80-
System.err.println(e.getMessage());
81-
System.exit(1);
8292
} catch (IOException e) {
83-
System.err.println("Unable to connect " + e.getMessage());
93+
logger.log(Level.SEVERE, "Unable to connect ", e);
8494
System.exit(2);
8595
} catch (RuntimeException e) {
86-
e.printStackTrace(System.err);
96+
logger.log(Level.SEVERE, e.getMessage(), e);
8797
System.exit(3);
8898
}
8999
}
90100

101+
// package private for testing
102+
static void propagateToSystemProperties(Properties properties) {
103+
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
104+
String key = entry.getKey().toString();
105+
String value = entry.getValue().toString();
106+
if (key.startsWith("javax.net.ssl.keyStore") || key.startsWith("javax.net.ssl.trustStore")) {
107+
if (System.getProperty(key) == null) {
108+
System.setProperty(key, value);
109+
}
110+
}
111+
}
112+
}
113+
91114
/**
92115
* Create {@link Properties} from command line options
93116
*
94117
* @param args application commandline arguments
95118
*/
96-
static Properties parseArgs(List<String> args)
97-
throws ArgumentsParsingException, ConfigurationException {
119+
static Properties parseArgs(List<String> args) throws InvalidArgumentException {
98120

99121
if (args.isEmpty()) {
100122
// empty properties from stdin or external file
101123
// config could still be provided through JVM system properties
102124
return new Properties();
103125
}
104126
if (args.size() != 2) {
105-
throw new ArgumentsParsingException("Exactly two arguments expected, got " + args.size());
127+
throw new InvalidArgumentException("Exactly two arguments expected, got " + args.size());
106128
}
107129
if (!args.get(0).equalsIgnoreCase(CONFIG_ARG)) {
108-
throw new ArgumentsParsingException("Unexpected first argument must be '" + CONFIG_ARG + "'");
130+
throw new InvalidArgumentException("Unexpected first argument must be '" + CONFIG_ARG + "'");
109131
}
110132

111133
String path = args.get(1);
@@ -116,27 +138,30 @@ static Properties parseArgs(List<String> args)
116138
}
117139
}
118140

119-
private static Properties loadPropertiesFromStdin() throws ConfigurationException {
141+
private static Properties loadPropertiesFromStdin() throws InvalidArgumentException {
120142
Properties properties = new Properties();
121143
try (InputStream is = new DataInputStream(System.in)) {
122144
properties.load(is);
123145
return properties;
124146
} catch (IOException e) {
125-
throw new ConfigurationException("Failed to read config properties from stdin", e);
147+
// an IO error is very unlikely here
148+
throw new InvalidArgumentException("Failed to read config properties from stdin", e);
126149
}
127150
}
128151

129-
private static Properties loadPropertiesFromPath(String path) throws ConfigurationException {
152+
private static Properties loadPropertiesFromPath(String path) throws InvalidArgumentException {
130153
Properties properties = new Properties();
131154
try (InputStream is = Files.newInputStream(Paths.get(path))) {
132155
properties.load(is);
133156
return properties;
134157
} catch (IOException e) {
135-
throw new ConfigurationException("Failed to read config properties file: '" + path + "'", e);
158+
throw new InvalidArgumentException(
159+
"Failed to read config properties file: '" + path + "'", e);
136160
}
137161
}
138162

139-
JmxScraper(JmxConnectorBuilder client, JmxMetricInsight service, JmxScraperConfig config) {
163+
private JmxScraper(
164+
JmxConnectorBuilder client, JmxMetricInsight service, JmxScraperConfig config) {
140165
this.client = client;
141166
this.service = service;
142167
this.config = config;

jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/ConfigurationException.java

-18
This file was deleted.

0 commit comments

Comments
 (0)