Skip to content

Commit 238a201

Browse files
jmx state metrics (#12369)
Co-authored-by: Jay DeLuca <[email protected]>
1 parent 925c920 commit 238a201

File tree

14 files changed

+746
-141
lines changed

14 files changed

+746
-141
lines changed

instrumentation/jmx-metrics/javaagent/README.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,67 @@ Thus, the above definitions will create several metrics, named `my.kafka.streams
221221

222222
The metric descriptions will remain undefined, unless they are provided by the queried MBeans.
223223

224+
### State Metrics
225+
226+
Some JMX attributes expose current state as a non-numeric MBean attribute, in order to capture those as metrics it is recommended to use the special `state` metric type.
227+
For example, with Tomcat connector, the `Catalina:type=Connector,port=*` MBean has `stateName` (of type `String`), we can define the following rule:
228+
229+
```yaml
230+
---
231+
rules:
232+
- bean: Catalina:type=Connector,port=*
233+
mapping:
234+
stateName:
235+
type: state
236+
metric: tomcat.connector
237+
metricAttribute:
238+
port: param(port)
239+
connector_state:
240+
ok: STARTED
241+
failed: [STOPPED,FAILED]
242+
degraded: '*'
243+
```
244+
245+
For a given value of `port`, let's say `8080` This will capture the `tomcat.connector.state` metric of type `updowncounter` with value `0` or `1` and the `state` metric attribute will have a value in [`ok`,`failed`,`degraded`].
246+
For every sample, 3 metrics will be captured for each value of `state` depending on the value of `stateName`:
247+
248+
When `stateName` = `STARTED`, we have:
249+
250+
- `tomcat.connector` value = `1`, attributes `port` = `8080` and `connector_state` = `ok`
251+
- `tomcat.connector` value = `0`, attributes `port` = `8080` and `connector_state` = `failed`
252+
- `tomcat.connector` value = `0`, attributes `port` = `8080` and `connector_state` = `degraded`
253+
254+
When `stateName` = `STOPPED` or `FAILED`, we have:
255+
256+
- `tomcat.connector` value = `0`, attributes `port` = `8080` and `connector_state` = `ok`
257+
- `tomcat.connector` value = `1`, attributes `port` = `8080` and `connector_state` = `failed`
258+
- `tomcat.connector` value = `0`, attributes `port` = `8080` and `connector_state` = `degraded`
259+
260+
For other values of `stateName`, we have:
261+
262+
- `tomcat.connector` value = `0`, attributes `port` = `8080` and `connector_state` = `ok`
263+
- `tomcat.connector` value = `0`, attributes `port` = `8080` and `connector_state` = `failed`
264+
- `tomcat.connector` value = `1`, attributes `port` = `8080` and `connector_state` = `degraded`
265+
266+
Each state key can be mapped to one or more values of the MBean attribute using:
267+
- a string literal or a string array
268+
- a `*` character to provide default option and avoid enumerating all values, this value must be quoted in YAML
269+
270+
Exactly one `*` value must be present in the mapping to ensure all possible values of the MBean attribute can be mapped to a state key.
271+
272+
The default value indicated by `*` does not require a dedicated state key. For example, if we want to have `connector_state` metric attribute with values `on` or `off`, we can use:
273+
```yaml
274+
connector_state:
275+
on: STARTED
276+
off: [STOPPED,FAILED,'*']
277+
```
278+
In the particular case where only two values are defined, we can simplify further by explicitly defining one state and rely on default for the other.
279+
```yaml
280+
connector_state:
281+
on: STARTED
282+
off: '*'
283+
```
284+
224285
### General Syntax
225286

226287
Here is the general description of the accepted configuration file syntax. The whole contents of the file is case-sensitive, with exception for `type` as described in the table below.

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ AttributeInfo getAttributeInfo(MBeanServerConnection connection, ObjectName obje
151151

152152
// Verify correctness of configuration by attempting to extract the metric value.
153153
// The value will be discarded, but its type will be checked.
154-
Object sampleValue = extractAttributeValue(connection, objectName, logger);
154+
Object sampleValue = getSampleValue(connection, objectName);
155155

156156
// Only numbers can be used to generate metric values
157157
if (sampleValue instanceof Number) {
@@ -194,6 +194,11 @@ AttributeInfo getAttributeInfo(MBeanServerConnection connection, ObjectName obje
194194
return null;
195195
}
196196

197+
@Nullable
198+
protected Object getSampleValue(MBeanServerConnection connection, ObjectName objectName) {
199+
return extractAttributeValue(connection, objectName, logger);
200+
}
201+
197202
/**
198203
* Extracts the specified attribute value. In case the value is a CompositeData, drills down into
199204
* it to find the correct singleton value (usually a Number or a String).
@@ -203,7 +208,7 @@ AttributeInfo getAttributeInfo(MBeanServerConnection connection, ObjectName obje
203208
* pattern
204209
* @param logger the logger to use, may be null. Typically we want to log any issues with the
205210
* attributes during MBean discovery, but once the attribute is successfully detected and
206-
* confirmed to be eligble for metric evaluation, any further attribute extraction
211+
* confirmed to be eligible for metric evaluation, any further attribute extraction
207212
* malfunctions will be silent to avoid flooding the log.
208213
* @return the attribute value, if found, or {@literal null} if an error occurred
209214
*/
@@ -253,7 +258,8 @@ private Object extractAttributeValue(MBeanServerConnection connection, ObjectNam
253258
}
254259

255260
@Nullable
256-
Number extractNumericalAttribute(MBeanServerConnection connection, ObjectName objectName) {
261+
protected Number extractNumericalAttribute(
262+
MBeanServerConnection connection, ObjectName objectName) {
257263
Object value = extractAttributeValue(connection, objectName);
258264
if (value instanceof Number) {
259265
return (Number) value;

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

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

8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.List;
811
import javax.annotation.Nullable;
12+
import javax.management.MalformedObjectNameException;
913
import javax.management.ObjectName;
1014
import javax.management.QueryExp;
1115

@@ -16,7 +20,7 @@
1620
public class BeanGroup {
1721
// How to specify the MBean(s)
1822
@Nullable private final QueryExp queryExp;
19-
private final ObjectName[] namePatterns;
23+
private final List<ObjectName> namePatterns;
2024

2125
/**
2226
* Constructor for BeanGroup.
@@ -25,17 +29,29 @@ public class BeanGroup {
2529
* @param namePatterns an array of ObjectNames used to look for MBeans; usually they will be
2630
* patterns. If multiple patterns are provided, they work as logical OR.
2731
*/
28-
public BeanGroup(@Nullable QueryExp queryExp, ObjectName... namePatterns) {
32+
private BeanGroup(@Nullable QueryExp queryExp, List<ObjectName> namePatterns) {
2933
this.queryExp = queryExp;
3034
this.namePatterns = namePatterns;
3135
}
3236

37+
public static BeanGroup forSingleBean(String bean) throws MalformedObjectNameException {
38+
return new BeanGroup(null, Collections.singletonList(new ObjectName(bean)));
39+
}
40+
41+
public static BeanGroup forBeans(List<String> beans) throws MalformedObjectNameException {
42+
List<ObjectName> list = new ArrayList<>();
43+
for (String name : beans) {
44+
list.add(new ObjectName(name));
45+
}
46+
return new BeanGroup(null, list);
47+
}
48+
3349
@Nullable
3450
QueryExp getQueryExp() {
3551
return queryExp;
3652
}
3753

38-
ObjectName[] getNamePatterns() {
54+
List<ObjectName> getNamePatterns() {
3955
return namePatterns;
4056
}
4157
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ public MetricAttribute(String name, MetricAttributeExtractor extractor) {
2222
this.extractor = extractor;
2323
}
2424

25+
public boolean isStateAttribute() {
26+
return extractor == null;
27+
}
28+
2529
public String getAttributeName() {
2630
return name;
2731
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

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

8+
import java.util.List;
9+
810
/**
911
* A class providing a complete definition on how to create an Open Telemetry metric out of the JMX
1012
* system: how to extract values from MBeans and how to model, name and decorate them with
@@ -74,7 +76,7 @@ public class MetricDef {
7476
private final BeanGroup beans;
7577

7678
// Describes how to get the metric values and their attributes, and how to report them
77-
private final MetricExtractor[] metricExtractors;
79+
private final List<MetricExtractor> metricExtractors;
7880

7981
/**
8082
* Constructor for MetricDef.
@@ -84,7 +86,7 @@ public class MetricDef {
8486
* MetricExtractor is provided, they should use unique metric names or unique metric
8587
* attributes
8688
*/
87-
public MetricDef(BeanGroup beans, MetricExtractor... metricExtractors) {
89+
public MetricDef(BeanGroup beans, List<MetricExtractor> metricExtractors) {
8890
this.beans = beans;
8991
this.metricExtractors = metricExtractors;
9092
}
@@ -93,7 +95,7 @@ BeanGroup getBeanGroup() {
9395
return beans;
9496
}
9597

96-
MetricExtractor[] getMetricExtractors() {
98+
List<MetricExtractor> getMetricExtractors() {
9799
return metricExtractors;
98100
}
99101
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

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

8+
import java.util.List;
89
import javax.annotation.Nullable;
910

1011
/**
@@ -22,14 +23,14 @@ public class MetricExtractor {
2223
private final BeanAttributeExtractor attributeExtractor;
2324

2425
// Defines the Measurement attributes to be used when reporting the metric value.
25-
private final MetricAttribute[] attributes;
26+
private final List<MetricAttribute> attributes;
2627

2728
@Nullable private volatile DetectionStatus status;
2829

2930
public MetricExtractor(
3031
BeanAttributeExtractor attributeExtractor,
3132
MetricInfo metricInfo,
32-
MetricAttribute... attributes) {
33+
List<MetricAttribute> attributes) {
3334
this.attributeExtractor = attributeExtractor;
3435
this.metricInfo = metricInfo;
3536
this.attributes = attributes;
@@ -43,7 +44,7 @@ BeanAttributeExtractor getMetricValueExtractor() {
4344
return attributeExtractor;
4445
}
4546

46-
MetricAttribute[] getAttributes() {
47+
List<MetricAttribute> getAttributes() {
4748
return attributes;
4849
}
4950

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ public class MetricInfo {
1919
public enum Type {
2020
COUNTER,
2121
UPDOWNCOUNTER,
22-
GAUGE
22+
GAUGE,
23+
/** state metric captured as updowncounter */
24+
STATE
2325
}
2426

2527
// How to report the metric using OpenTelemetry API
@@ -44,21 +46,21 @@ public MetricInfo(
4446
this.type = type == null ? Type.GAUGE : type;
4547
}
4648

47-
String getMetricName() {
49+
public String getMetricName() {
4850
return metricName;
4951
}
5052

5153
@Nullable
52-
String getDescription() {
54+
public String getDescription() {
5355
return description;
5456
}
5557

5658
@Nullable
57-
String getUnit() {
59+
public String getUnit() {
5860
return unit;
5961
}
6062

61-
Type getType() {
63+
public Type getType() {
6264
return type;
6365
}
6466
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,13 @@ void enrollExtractor(
120120
}
121121
logger.log(INFO, "Created Gauge for {0}", metricName);
122122
}
123+
break;
124+
// CHECKSTYLE:OFF
125+
case STATE:
126+
{
127+
// CHECKSTYLE:ON
128+
throw new IllegalStateException("state metrics should not be registered");
129+
}
123130
}
124131
}
125132

@@ -173,9 +180,8 @@ static Consumer<ObservableLongMeasurement> longTypeCallback(MetricExtractor extr
173180
*/
174181
static Attributes createMetricAttributes(
175182
MBeanServerConnection connection, ObjectName objectName, MetricExtractor extractor) {
176-
MetricAttribute[] metricAttributes = extractor.getAttributes();
177183
AttributesBuilder attrBuilder = Attributes.builder();
178-
for (MetricAttribute metricAttribute : metricAttributes) {
184+
for (MetricAttribute metricAttribute : extractor.getAttributes()) {
179185
String attributeValue = metricAttribute.acquireAttributeValue(connection, objectName);
180186
if (attributeValue != null) {
181187
attrBuilder = attrBuilder.put(metricAttribute.getAttributeName(), attributeValue);

0 commit comments

Comments
 (0)