Skip to content

Commit be7d07c

Browse files
authored
jmx idiomatic library API - part 1 (#15220)
1 parent 545f3b4 commit be7d07c

File tree

9 files changed

+271
-118
lines changed

9 files changed

+271
-118
lines changed

instrumentation/jmx-metrics/javaagent/src/main/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstaller.java

Lines changed: 19 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,43 @@
55

66
package io.opentelemetry.instrumentation.javaagent.jmx;
77

8-
import static java.util.logging.Level.FINE;
9-
import static java.util.logging.Level.INFO;
10-
118
import com.google.auto.service.AutoService;
129
import io.opentelemetry.api.GlobalOpenTelemetry;
13-
import io.opentelemetry.instrumentation.jmx.engine.JmxMetricInsight;
14-
import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration;
15-
import io.opentelemetry.instrumentation.jmx.yaml.RuleParser;
10+
import io.opentelemetry.instrumentation.jmx.JmxTelemetry;
11+
import io.opentelemetry.instrumentation.jmx.JmxTelemetryBuilder;
1612
import io.opentelemetry.javaagent.extension.AgentListener;
1713
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
1814
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
1915
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
20-
import java.io.InputStream;
21-
import java.nio.file.Files;
2216
import java.nio.file.Paths;
2317
import java.time.Duration;
24-
import java.util.List;
18+
import java.util.logging.Level;
19+
import java.util.logging.Logger;
2520

2621
/** An {@link AgentListener} that enables JMX metrics during agent startup. */
2722
@AutoService(AgentListener.class)
2823
public class JmxMetricInsightInstaller implements AgentListener {
2924

25+
private static final Logger logger = Logger.getLogger(JmxMetricInsightInstaller.class.getName());
26+
3027
@Override
3128
public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
3229
ConfigProperties config = AutoConfigureUtil.getConfig(autoConfiguredSdk);
3330

3431
if (config.getBoolean("otel.jmx.enabled", true)) {
35-
JmxMetricInsight service =
36-
JmxMetricInsight.createService(
37-
GlobalOpenTelemetry.get(), beanDiscoveryDelay(config).toMillis());
38-
MetricConfiguration conf = buildMetricConfiguration(config);
39-
service.startLocal(conf);
32+
JmxTelemetryBuilder jmx =
33+
JmxTelemetry.builder(GlobalOpenTelemetry.get())
34+
.beanDiscoveryDelay(beanDiscoveryDelay(config));
35+
36+
try {
37+
config.getList("otel.jmx.config").stream().map(Paths::get).forEach(jmx::addCustomRules);
38+
config.getList("otel.jmx.target.system").forEach(jmx::addClassPathRules);
39+
} catch (RuntimeException e) {
40+
// for now only log JMX errors as they do not prevent agent startup
41+
logger.log(Level.SEVERE, "Error while loading JMX configuration", e);
42+
}
43+
44+
jmx.build().start();
4045
}
4146
}
4247

@@ -50,58 +55,4 @@ private static Duration beanDiscoveryDelay(ConfigProperties configProperties) {
5055
// It makes sense for both of these values to be similar.
5156
return configProperties.getDuration("otel.metric.export.interval", Duration.ofMinutes(1));
5257
}
53-
54-
private static String resourceFor(String platform) {
55-
return "/jmx/rules/" + platform + ".yaml";
56-
}
57-
58-
private static void addRulesForPlatform(String platform, MetricConfiguration conf) {
59-
String yamlResource = resourceFor(platform);
60-
try (InputStream inputStream =
61-
JmxMetricInsightInstaller.class.getResourceAsStream(yamlResource)) {
62-
if (inputStream != null) {
63-
JmxMetricInsight.getLogger().log(FINE, "Opened input stream {0}", yamlResource);
64-
RuleParser parserInstance = RuleParser.get();
65-
parserInstance.addMetricDefsTo(conf, inputStream, platform);
66-
} else {
67-
JmxMetricInsight.getLogger().log(INFO, "No support found for {0}", platform);
68-
}
69-
} catch (Exception e) {
70-
JmxMetricInsight.getLogger().warning(e.getMessage());
71-
}
72-
}
73-
74-
private static void buildFromDefaultRules(
75-
MetricConfiguration conf, ConfigProperties configProperties) {
76-
List<String> platforms = configProperties.getList("otel.jmx.target.system");
77-
for (String platform : platforms) {
78-
addRulesForPlatform(platform, conf);
79-
}
80-
}
81-
82-
private static void buildFromUserRules(
83-
MetricConfiguration conf, ConfigProperties configProperties) {
84-
List<String> configFiles = configProperties.getList("otel.jmx.config");
85-
for (String configFile : configFiles) {
86-
JmxMetricInsight.getLogger().log(FINE, "JMX config file name: {0}", configFile);
87-
RuleParser parserInstance = RuleParser.get();
88-
try (InputStream inputStream = Files.newInputStream(Paths.get(configFile))) {
89-
parserInstance.addMetricDefsTo(conf, inputStream, configFile);
90-
} catch (Exception e) {
91-
// yaml parsing errors are caught and logged inside of addMetricDefsTo
92-
// only file access related exceptions are expected here
93-
JmxMetricInsight.getLogger().warning(e.toString());
94-
}
95-
}
96-
}
97-
98-
private static MetricConfiguration buildMetricConfiguration(ConfigProperties configProperties) {
99-
MetricConfiguration metricConfiguration = new MetricConfiguration();
100-
101-
buildFromDefaultRules(metricConfiguration, configProperties);
102-
103-
buildFromUserRules(metricConfiguration, configProperties);
104-
105-
return metricConfiguration;
106-
}
10758
}

instrumentation/jmx-metrics/javaagent/src/test/java/io/opentelemetry/instrumentation/javaagent/jmx/JmxMetricInsightInstallerTest.java

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,13 @@
77

88
import static org.assertj.core.api.Assertions.assertThat;
99

10-
import io.opentelemetry.instrumentation.jmx.yaml.JmxConfig;
11-
import io.opentelemetry.instrumentation.jmx.yaml.JmxRule;
10+
import io.opentelemetry.api.OpenTelemetry;
11+
import io.opentelemetry.instrumentation.jmx.JmxTelemetry;
1212
import io.opentelemetry.instrumentation.jmx.yaml.RuleParser;
13-
import java.io.File;
14-
import java.io.FileInputStream;
15-
import java.io.InputStream;
16-
import java.nio.file.Files;
1713
import java.nio.file.Path;
1814
import java.nio.file.Paths;
1915
import java.util.Arrays;
2016
import java.util.HashSet;
21-
import java.util.List;
2217
import java.util.Set;
2318
import org.junit.jupiter.api.Test;
2419

@@ -39,34 +34,14 @@ void testToVerifyExistingRulesAreValid() throws Exception {
3934
assertThat(parser).isNotNull();
4035

4136
Path path = Paths.get(PATH_TO_ALL_EXISTING_RULES);
42-
assertThat(Files.exists(path)).isTrue();
37+
assertThat(path).isNotEmptyDirectory();
4338

44-
File existingRulesDir = path.toFile();
45-
File[] existingRules = existingRulesDir.listFiles();
46-
Set<String> filesChecked = new HashSet<>();
39+
for (String file : FILES_TO_BE_TESTED) {
40+
Path filePath = path.resolve(file);
41+
assertThat(filePath).isRegularFile();
4742

48-
for (File file : existingRules) {
49-
// make sure we only test the files that we supposed to test
50-
String fileName = file.getName();
51-
if (FILES_TO_BE_TESTED.contains(fileName)) {
52-
testRulesAreValid(file, parser);
53-
filesChecked.add(fileName);
54-
}
55-
}
56-
// make sure we checked all the files that are supposed to be here
57-
assertThat(filesChecked).isEqualTo(FILES_TO_BE_TESTED);
58-
}
59-
60-
void testRulesAreValid(File file, RuleParser parser) throws Exception {
61-
try (InputStream inputStream = new FileInputStream(file)) {
62-
JmxConfig config = parser.loadConfig(inputStream);
63-
assertThat(config).isNotNull();
64-
65-
List<JmxRule> defs = config.getRules();
66-
// make sure all the rules in that file are valid
67-
for (JmxRule rule : defs) {
68-
rule.buildMetricDef();
69-
}
43+
// loading rules from direct file access
44+
JmxTelemetry.builder(OpenTelemetry.noop()).addCustomRules(filePath);
7045
}
7146
}
7247
}

instrumentation/jmx-metrics/library/README.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,21 @@ implementation("io.opentelemetry.instrumentation:opentelemetry-jmx-metrics:OPENT
3232

3333
```java
3434
import io.opentelemetry.api.OpenTelemetry;
35-
import io.opentelemetry.instrumentation.jmx.engine.JmxMetricInsight;
36-
import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration;
35+
import io.opentelemetry.instrumentation.jmx.JmxTelemetry;
36+
import io.opentelemetry.instrumentation.jmx.JmxTelemetryBuilder;
3737

3838
// Get an OpenTelemetry instance
39-
OpenTelemetry openTelemetry = ...;
40-
41-
JmxMetricInsight jmxMetricInsight = JmxMetricInsight.createService(openTelemetry, 5000);
42-
43-
// Configure your JMX metrics
44-
MetricConfiguration config = new MetricConfiguration();
45-
46-
jmxMetricInsight.startLocal(config);
39+
OpenTelemetry openTelemetry = ...
40+
41+
JmxTelemetry jmxTelemetry = JmxTelemetry.builder(openTelemetry)
42+
// Configure included metrics (optional)
43+
.addClasspathRules("tomcat")
44+
.addClasspathRules("jetty")
45+
// Configure custom metrics (optional)
46+
.addCustomRules("/path/to/custom-jmx.yaml")
47+
// delay bean discovery by 5 seconds
48+
.beanDiscoveryDelay(5000)
49+
.build();
50+
51+
jmxTelemetry.startLocal();
4752
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.jmx;
7+
8+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
9+
import io.opentelemetry.api.OpenTelemetry;
10+
import io.opentelemetry.instrumentation.jmx.engine.JmxMetricInsight;
11+
import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration;
12+
import java.util.List;
13+
import java.util.function.Supplier;
14+
import javax.management.MBeanServerConnection;
15+
import javax.management.MBeanServerFactory;
16+
17+
/** Entrypoint for JMX metrics Insights */
18+
public final class JmxTelemetry {
19+
20+
private final JmxMetricInsight service;
21+
private final MetricConfiguration metricConfiguration;
22+
23+
/** Returns a new {@link JmxTelemetryBuilder} configured with the given {@link OpenTelemetry}. */
24+
public static JmxTelemetryBuilder builder(OpenTelemetry openTelemetry) {
25+
return new JmxTelemetryBuilder(openTelemetry);
26+
}
27+
28+
JmxTelemetry(
29+
OpenTelemetry openTelemetry, long discoveryDelayMs, MetricConfiguration metricConfiguration) {
30+
this.service = JmxMetricInsight.createService(openTelemetry, discoveryDelayMs);
31+
this.metricConfiguration = metricConfiguration;
32+
}
33+
34+
/**
35+
* Starts JMX metrics collection on current JVM
36+
*
37+
* @return this
38+
*/
39+
@CanIgnoreReturnValue
40+
public JmxTelemetry start() {
41+
return this.start(() -> MBeanServerFactory.findMBeanServer(null));
42+
}
43+
44+
/**
45+
* Starts JMX metrics collection on provided (local or remote) connections
46+
*
47+
* @param connections connection provider
48+
* @return this
49+
*/
50+
@CanIgnoreReturnValue
51+
public JmxTelemetry start(Supplier<List<? extends MBeanServerConnection>> connections) {
52+
service.start(metricConfiguration, connections);
53+
return this;
54+
}
55+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.instrumentation.jmx;
7+
8+
import static java.util.logging.Level.FINE;
9+
10+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
11+
import io.opentelemetry.api.OpenTelemetry;
12+
import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration;
13+
import io.opentelemetry.instrumentation.jmx.yaml.RuleParser;
14+
import java.io.IOException;
15+
import java.io.InputStream;
16+
import java.nio.file.Files;
17+
import java.nio.file.Path;
18+
import java.time.Duration;
19+
import java.util.logging.Logger;
20+
21+
/** Builder for {@link JmxTelemetry} */
22+
public final class JmxTelemetryBuilder {
23+
24+
private static final Logger logger = Logger.getLogger(JmxTelemetryBuilder.class.getName());
25+
26+
private final OpenTelemetry openTelemetry;
27+
private final MetricConfiguration metricConfiguration;
28+
private long discoveryDelayMs;
29+
30+
JmxTelemetryBuilder(OpenTelemetry openTelemetry) {
31+
this.openTelemetry = openTelemetry;
32+
this.discoveryDelayMs = 0;
33+
this.metricConfiguration = new MetricConfiguration();
34+
}
35+
36+
/**
37+
* Sets initial delay for MBean discovery
38+
*
39+
* @param delay delay
40+
* @return builder instance
41+
*/
42+
@CanIgnoreReturnValue
43+
public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) {
44+
if (delay.isNegative()) {
45+
throw new IllegalArgumentException("delay must be positive or zero");
46+
}
47+
this.discoveryDelayMs = delay.toMillis();
48+
return this;
49+
}
50+
51+
/**
52+
* Adds built-in JMX rules from classpath resource
53+
*
54+
* @param target name of target in /jmx/rules/{target}.yaml classpath resource
55+
* @return builder instance
56+
* @throws IllegalArgumentException when classpath resource does not exist or can't be parsed
57+
*/
58+
@CanIgnoreReturnValue
59+
public JmxTelemetryBuilder addClassPathRules(String target) {
60+
String yamlResource = String.format("jmx/rules/%s.yaml", target);
61+
try (InputStream inputStream =
62+
JmxTelemetryBuilder.class.getClassLoader().getResourceAsStream(yamlResource)) {
63+
if (inputStream == null) {
64+
throw new IllegalArgumentException("JMX rules not found in classpath: " + yamlResource);
65+
}
66+
logger.log(FINE, "Adding JMX config from classpath for {0}", yamlResource);
67+
RuleParser parserInstance = RuleParser.get();
68+
parserInstance.addMetricDefsTo(metricConfiguration, inputStream, target);
69+
} catch (Exception e) {
70+
throw new IllegalArgumentException(
71+
"Unable to load JMX rules from classpath: " + yamlResource, e);
72+
}
73+
return this;
74+
}
75+
76+
/**
77+
* Adds custom JMX rules from file system path
78+
*
79+
* @param path path to yaml file
80+
* @return builder instance
81+
* @throws IllegalArgumentException when classpath resource does not exist or can't be parsed
82+
*/
83+
@CanIgnoreReturnValue
84+
public JmxTelemetryBuilder addCustomRules(Path path) {
85+
logger.log(FINE, "Adding JMX config from file: {0}", path);
86+
RuleParser parserInstance = RuleParser.get();
87+
try (InputStream inputStream = Files.newInputStream(path)) {
88+
parserInstance.addMetricDefsTo(metricConfiguration, inputStream, path.toString());
89+
} catch (IOException e) {
90+
throw new IllegalArgumentException("Unable to load JMX rules in path: " + path, e);
91+
}
92+
return this;
93+
}
94+
95+
public JmxTelemetry build() {
96+
return new JmxTelemetry(openTelemetry, discoveryDelayMs, metricConfiguration);
97+
}
98+
}

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/engine/JmxMetricInsight.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,19 @@ public void startLocal(MetricConfiguration conf) {
5252
* @param conf metric configuration
5353
* @param connections supplier for list of remote connections
5454
*/
55+
@SuppressWarnings("unused") // used by jmx-scraper with remote connection
5556
public void startRemote(
5657
MetricConfiguration conf, Supplier<List<? extends MBeanServerConnection>> connections) {
5758
start(conf, connections);
5859
}
5960

60-
private void start(
61+
/**
62+
* Starts metric registration on the provided list of connections
63+
*
64+
* @param conf metric configuration
65+
* @param connections supplier for list of connections (remote or local)
66+
*/
67+
public void start(
6168
MetricConfiguration conf, Supplier<List<? extends MBeanServerConnection>> connections) {
6269
if (conf.isEmpty()) {
6370
logger.log(

0 commit comments

Comments
 (0)