Skip to content

Commit 4d54532

Browse files
authored
jmx-metrics: allow any resource path for classpath resources rules (#15413)
1 parent 090f148 commit 4d54532

File tree

6 files changed

+103
-46
lines changed

6 files changed

+103
-46
lines changed

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

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
1414
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
1515
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
16+
import java.io.InputStream;
17+
import java.nio.file.Path;
1618
import java.nio.file.Paths;
1719
import java.time.Duration;
1820
import java.util.logging.Level;
@@ -33,18 +35,37 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredSdk) {
3335
JmxTelemetry.builder(GlobalOpenTelemetry.get())
3436
.beanDiscoveryDelay(beanDiscoveryDelay(config));
3537

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-
}
38+
config.getList("otel.jmx.config").stream()
39+
.map(Paths::get)
40+
.forEach(path -> addFileRules(path, jmx));
41+
config.getList("otel.jmx.target.system").forEach(target -> addClasspathRules(target, jmx));
4342

4443
jmx.build().start();
4544
}
4645
}
4746

47+
private static void addFileRules(Path path, JmxTelemetryBuilder builder) {
48+
try {
49+
builder.addRules(path);
50+
} catch (RuntimeException e) {
51+
// for now only log JMX metric configuration errors as they do not prevent agent startup
52+
logger.log(Level.SEVERE, "Error while loading JMX configuration from " + path, e);
53+
}
54+
}
55+
56+
private static void addClasspathRules(String target, JmxTelemetryBuilder builder) {
57+
ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader();
58+
String resource = String.format("jmx/rules/%s.yaml", target);
59+
InputStream input = classLoader.getResourceAsStream(resource);
60+
try {
61+
builder.addRules(input);
62+
} catch (RuntimeException e) {
63+
// for now only log JMX metric configuration errors as they do not prevent agent startup
64+
logger.log(
65+
Level.SEVERE, "Error while loading JMX configuration from classpath " + resource, e);
66+
}
67+
}
68+
4869
private static Duration beanDiscoveryDelay(ConfigProperties configProperties) {
4970
Duration discoveryDelay = configProperties.getDuration("otel.jmx.discovery.delay");
5071
if (discoveryDelay != null) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ void testToVerifyExistingRulesAreValid() throws Exception {
4141
assertThat(filePath).isRegularFile();
4242

4343
// loading rules from direct file access
44-
JmxTelemetry.builder(OpenTelemetry.noop()).addCustomRules(filePath);
44+
JmxTelemetry.builder(OpenTelemetry.noop()).addRules(filePath);
4545
}
4646
}
4747
}

instrumentation/jmx-metrics/library/README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,20 @@ import io.opentelemetry.api.OpenTelemetry;
3535
import io.opentelemetry.instrumentation.jmx.JmxTelemetry;
3636
import io.opentelemetry.instrumentation.jmx.JmxTelemetryBuilder;
3737

38+
import java.time.Duration;
39+
3840
// Get an OpenTelemetry instance
39-
OpenTelemetry openTelemetry = ...
41+
OpenTelemetry openTelemetry = ...;
4042

4143
JmxTelemetry jmxTelemetry = JmxTelemetry.builder(openTelemetry)
4244
// Configure included metrics (optional)
43-
.addClasspathRules("tomcat")
44-
.addClasspathRules("jetty")
45+
.addRules(JmxTelemetry.class.getClassLoader().getResourceAsStream("jmx/rules/jetty.yaml"), "jetty")
46+
.addRules(JmxTelemetry.class.getClassLoader().getResourceAsStream("jmx/rules/tomcat.yaml"), "tomcat")
4547
// Configure custom metrics (optional)
46-
.addCustomRules("/path/to/custom-jmx.yaml")
48+
.addRules(Paths.get("/path/to/custom-jmx.yaml"))
4749
// delay bean discovery by 5 seconds
48-
.beanDiscoveryDelay(5000)
50+
.beanDiscoveryDelay(Duration.ofSeconds(5))
4951
.build();
5052

51-
jmxTelemetry.startLocal();
53+
jmxTelemetry.start();
5254
```

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

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,47 +49,73 @@ public JmxTelemetryBuilder beanDiscoveryDelay(Duration delay) {
4949
}
5050

5151
/**
52-
* Adds built-in JMX rules from classpath resource
52+
* Adds built-in JMX rules from classpath resource.
5353
*
5454
* @param target name of target in /jmx/rules/{target}.yaml classpath resource
5555
* @return builder instance
5656
* @throws IllegalArgumentException when classpath resource does not exist or can't be parsed
5757
*/
58+
// TODO: deprecate this method after 2.23.0 release in favor of addRules
5859
@CanIgnoreReturnValue
5960
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) {
61+
String resourcePath = String.format("jmx/rules/%s.yaml", target);
62+
ClassLoader classLoader = JmxTelemetryBuilder.class.getClassLoader();
63+
logger.log(FINE, "Adding JMX config from classpath {0}", resourcePath);
64+
try (InputStream inputStream = classLoader.getResourceAsStream(resourcePath)) {
65+
return addRules(inputStream);
66+
} catch (IOException e) {
7067
throw new IllegalArgumentException(
71-
"Unable to load JMX rules from classpath: " + yamlResource, e);
68+
"Unable to load JMX rules from resource " + resourcePath, e);
7269
}
70+
}
71+
72+
/**
73+
* Adds JMX rules from input stream
74+
*
75+
* @param input input to read rules from
76+
* @throws IllegalArgumentException when input is {@literal null} or can't be parsed
77+
*/
78+
@CanIgnoreReturnValue
79+
public JmxTelemetryBuilder addRules(InputStream input) {
80+
if (input == null) {
81+
throw new IllegalArgumentException("missing JMX rules");
82+
}
83+
RuleParser parserInstance = RuleParser.get();
84+
parserInstance.addMetricDefsTo(metricConfiguration, input);
7385
return this;
7486
}
7587

7688
/**
77-
* Adds custom JMX rules from file system path
89+
* Adds JMX rules from file system path
7890
*
7991
* @param path path to yaml file
8092
* @return builder instance
81-
* @throws IllegalArgumentException when classpath resource does not exist or can't be parsed
93+
* @throws IllegalArgumentException in case of parsing errors or when file does not exist
8294
*/
8395
@CanIgnoreReturnValue
84-
public JmxTelemetryBuilder addCustomRules(Path path) {
85-
logger.log(FINE, "Adding JMX config from file: {0}", path);
86-
RuleParser parserInstance = RuleParser.get();
96+
public JmxTelemetryBuilder addRules(Path path) {
97+
if (path == null) {
98+
throw new IllegalArgumentException("missing JMX rules");
99+
}
87100
try (InputStream inputStream = Files.newInputStream(path)) {
88-
parserInstance.addMetricDefsTo(metricConfiguration, inputStream, path.toString());
101+
logger.log(FINE, "Adding JMX config from file {0}", path);
102+
return addRules(inputStream);
89103
} catch (IOException e) {
90-
throw new IllegalArgumentException("Unable to load JMX rules in path: " + path, e);
104+
throw new IllegalArgumentException("Unable to load JMX rules from: " + path, e);
91105
}
92-
return this;
106+
}
107+
108+
/**
109+
* Adds custom JMX rules from file system path
110+
*
111+
* @param path path to yaml file
112+
* @return builder instance
113+
* @throws IllegalArgumentException when classpath resource does not exist or can't be parsed
114+
*/
115+
// TODO: deprecate this method after 2.23.0 release in favor of addRules
116+
@CanIgnoreReturnValue
117+
public JmxTelemetryBuilder addCustomRules(Path path) {
118+
return addRules(path);
93119
}
94120

95121
public JmxTelemetry build() {

instrumentation/jmx-metrics/library/src/main/java/io/opentelemetry/instrumentation/jmx/yaml/RuleParser.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
package io.opentelemetry.instrumentation.jmx.yaml;
77

88
import static java.util.Collections.emptyList;
9-
import static java.util.logging.Level.INFO;
9+
import static java.util.logging.Level.FINE;
1010

1111
import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration;
1212
import java.io.InputStream;
@@ -162,21 +162,19 @@ private static void failOnExtraKeys(Map<String, Object> yaml) {
162162
*
163163
* @param conf the metric configuration
164164
* @param is the InputStream with the YAML rules
165-
* @param id identifier of the YAML ruleset, such as a filename
166165
* @throws IllegalArgumentException when unable to parse YAML
167166
*/
168-
public void addMetricDefsTo(MetricConfiguration conf, InputStream is, String id) {
167+
public void addMetricDefsTo(MetricConfiguration conf, InputStream is) {
169168
try {
170169
JmxConfig config = loadConfig(is);
171-
logger.log(INFO, "{0}: found {1} metric rules", new Object[] {id, config.getRules().size()});
170+
logger.log(FINE, "found {1} metric rules", config.getRules().size());
172171
config.addMetricDefsTo(conf);
173172
} catch (Exception exception) {
174173
// It is essential that the parser exception is made visible to the user.
175174
// It contains contextual information about any syntax issues found by the parser.
176175
String msg =
177176
String.format(
178-
"Failed to parse YAML rules from %s: %s %s",
179-
id, rootCause(exception), exception.getMessage());
177+
"Failed to parse YAML rules : %s %s", rootCause(exception), exception.getMessage());
180178
throw new IllegalArgumentException(msg, exception);
181179
}
182180
}

instrumentation/jmx-metrics/library/src/test/java/io/opentelemetry/instrumentation/jmx/JmxTelemetryTest.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1010

1111
import io.opentelemetry.api.OpenTelemetry;
12+
import java.io.InputStream;
1213
import java.nio.charset.StandardCharsets;
1314
import java.nio.file.Files;
1415
import java.nio.file.Path;
@@ -25,30 +26,39 @@ void createDefault() {
2526
}
2627

2728
@Test
28-
void missingClasspathTarget() {
29+
void throwsExceptionOnNullInput() {
2930
JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop());
30-
assertThatThrownBy(() -> builder.addClassPathRules("should-not-exist"))
31+
assertThatThrownBy(() -> builder.addRules((InputStream) null))
32+
.isInstanceOf(IllegalArgumentException.class);
33+
assertThatThrownBy(() -> builder.addRules((Path) null))
3134
.isInstanceOf(IllegalArgumentException.class);
3235
}
3336

3437
@Test
3538
void invalidClasspathTarget() {
3639
JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop());
37-
assertThatThrownBy(() -> builder.addClassPathRules("invalid"))
40+
assertThatThrownBy(() -> addClasspathRules(builder, "jmx/rules/invalid.yaml"))
3841
.isInstanceOf(IllegalArgumentException.class);
3942
}
4043

4144
@Test
42-
void knownClassPathTarget() {
43-
JmxTelemetry.builder(OpenTelemetry.noop()).addClassPathRules("jvm").build();
45+
void knownValidYaml() {
46+
JmxTelemetryBuilder jmxtelemetry = JmxTelemetry.builder(OpenTelemetry.noop());
47+
addClasspathRules(jmxtelemetry, "jmx/rules/jvm.yaml");
48+
assertThat(jmxtelemetry.build()).isNotNull();
49+
}
50+
51+
private static void addClasspathRules(JmxTelemetryBuilder builder, String path) {
52+
InputStream input = JmxTelemetryTest.class.getClassLoader().getResourceAsStream(path);
53+
builder.addRules(input);
4454
}
4555

4656
@Test
4757
void invalidExternalYaml(@TempDir Path dir) throws Exception {
4858
Path invalid = Files.createTempFile(dir, "invalid", ".yaml");
4959
Files.write(invalid, ":this !is /not YAML".getBytes(StandardCharsets.UTF_8));
5060
JmxTelemetryBuilder builder = JmxTelemetry.builder(OpenTelemetry.noop());
51-
assertThatThrownBy(() -> builder.addCustomRules(invalid))
61+
assertThatThrownBy(() -> builder.addRules(invalid))
5262
.isInstanceOf(IllegalArgumentException.class);
5363
}
5464

0 commit comments

Comments
 (0)