Skip to content

Commit 181f998

Browse files
authored
jmx filter negative values in yaml (#13589)
1 parent c7e601f commit 181f998

File tree

7 files changed

+240
-47
lines changed

7 files changed

+240
-47
lines changed

instrumentation/jmx-metrics/javaagent/README.md

+43-17
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,10 @@ If such a conversion is not available, then an error is reported during JMX metr
320320
Currently available unit conversions:
321321

322322
| `sourceUnit` | `unit` |
323-
|--------------|-------|
324-
| ms | s |
325-
| us | s |
326-
| ns | s |
323+
|--------------|--------|
324+
| ms | s |
325+
| us | s |
326+
| ns | s |
327327

328328
Example of defining unit conversion in yaml file:
329329
```yaml
@@ -341,6 +341,29 @@ rules:
341341
```
342342
`sourceUnit` can also be defined on rule level (see [Making shortcuts](#making-shortcuts))
343343

344+
### Filtering negative values
345+
346+
Sometimes a negative value is returned by the MBean implementation when a metric is not available or not supported.
347+
For example, [`OperatingSystemMXBean.getProcessCpuLoad`](https://docs.oracle.com/javase/7/docs/jre/api/management/extension/com/sun/management/OperatingSystemMXBean.html#getProcessCpuLoad()) can return a negative value.
348+
349+
In this case, it is recommended to filter out the negative values by setting the `dropNegativeValues` metric (or rule) property to `true`, it is set to `false` by default.
350+
351+
```yaml
352+
rules:
353+
- bean: java.lang:type=OperatingSystem
354+
# can also be set at rule-level (with lower priority)
355+
dropNegativeValues: false
356+
mapping:
357+
# jvm.cpu.recent_utilization
358+
ProcessCpuLoad:
359+
metric: jvm.cpu.recent_utilization
360+
type: gauge
361+
unit: '1'
362+
# setting dropNegativeValues at metric level has priority over rule level.
363+
dropNegativeValues: true
364+
desc: Recent CPU utilization for the process as reported by the JVM.
365+
```
366+
344367
### General Syntax
345368

346369
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.
@@ -356,12 +379,14 @@ rules: # start of list of configuration rules
356379
prefix: <METRIC_NAME_PREFIX> # optional, useful for avoiding specifying metric names below
357380
unit: <UNIT> # optional, redefines the default unit for the whole rule
358381
type: <TYPE> # optional, redefines the default type for the whole rule
382+
dropNegativeValues: <BOOL> # optional, redefines if negative values are dropped for the whole rule
359383
mapping:
360384
<BEANATTR1>: # an MBean attribute name defining the metric value
361385
metric: <METRIC_NAME1> # metric name will be <METRIC_NAME_PREFIX><METRIC_NAME1>
362386
type: <TYPE> # optional, the default type is gauge
363387
desc: <DESCRIPTION1> # optional
364388
unit: <UNIT1> # optional
389+
dropNegativeValues: <BOOL> # optional, defines if negative values are dropped for the metric
365390
metricAttribute: # optional, will be used in addition to the shared metric attributes above
366391
<ATTRIBUTE3>: const(<STR>) # direct value for the metric attribute
367392
<BEANATTR2>: # use a.b to get access into CompositeData
@@ -381,19 +406,20 @@ rules: # start of list of configuration rules
381406

382407
The following table explains the used terms with more details.
383408

384-
| Syntactic Element | Description |
385-
| ---------------- | --------------- |
386-
| OBJECTNAME | A syntactically valid string representing an ObjectName (see [ObjectName constructor](https://docs.oracle.com/javase/8/docs/api/javax/management/ObjectName.html#ObjectName-java.lang.String-)). |
387-
| ATTRIBUTE | Any well-formed string that can be used as a metric [attribute](https://opentelemetry.io/docs/reference/specification/common/#attribute) key. |
388-
| ATTR | A non-empty string used as a name of the MBean attribute. The MBean attribute value must be a String, otherwise the specified metric attribute will not be used. |
389-
| PARAM | A non-empty string used as a property key in the ObjectName identifying the MBean which provides the metric value. If the ObjectName does not have a property with the given key, the specified metric attribute will not be used. |
390-
| METRIC_NAME_PREFIX | Any non-empty string which will be prepended to the specified metric (instrument) names. |
391-
| METRIC_NAME | Any non-empty string. The string, prefixed by the optional prefix (see above) must satisfy [instrument naming rule](https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-naming-rule). |
392-
| TYPE | One of `counter`, `updowncounter`, or `gauge`. The default is `gauge`. This value is case insensitive. |
393-
| DESCRIPTION | Any string to be used as human-readable [description](https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-description) of the metric. If the description is not provided by the rule, an attempt will be made to extract one automatically from the corresponding MBean. |
394-
| UNIT | A string identifying the [unit](https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-unit) of measurements reported by the metric. Enclose the string in single or double quotes if using unit annotations. |
395-
| STR | Any string to be used directly as the metric attribute value. |
396-
| BEANATTR | A non-empty string representing the MBean attribute defining the metric value. The attribute value must be a number. Special dot-notation _attributeName.itemName_ can be used to access numerical items within attributes of [CompositeType](https://docs.oracle.com/javase/8/docs/api/javax/management/openmbean/CompositeType.html). If a dot happens to be an integral part of the MBean attribute name, it must be escaped by backslash (`\`). |
409+
| Syntactic Element | Description |
410+
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
411+
| OBJECTNAME | A syntactically valid string representing an ObjectName (see [ObjectName constructor](https://docs.oracle.com/javase/8/docs/api/javax/management/ObjectName.html#ObjectName-java.lang.String-)). |
412+
| ATTRIBUTE | Any well-formed string that can be used as a metric [attribute](https://opentelemetry.io/docs/reference/specification/common/#attribute) key. |
413+
| ATTR | A non-empty string used as a name of the MBean attribute. The MBean attribute value must be a String, otherwise the specified metric attribute will not be used. |
414+
| PARAM | A non-empty string used as a property key in the ObjectName identifying the MBean which provides the metric value. If the ObjectName does not have a property with the given key, the specified metric attribute will not be used. |
415+
| METRIC_NAME_PREFIX | Any non-empty string which will be prepended to the specified metric (instrument) names. |
416+
| METRIC_NAME | Any non-empty string. The string, prefixed by the optional prefix (see above) must satisfy [instrument naming rule](https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-naming-rule). |
417+
| TYPE | One of `counter`, `updowncounter`, or `gauge`. The default is `gauge`. This value is case insensitive. |
418+
| DESCRIPTION | Any string to be used as human-readable [description](https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-description) of the metric. If the description is not provided by the rule, an attempt will be made to extract one automatically from the corresponding MBean. |
419+
| UNIT | A string identifying the [unit](https://opentelemetry.io/docs/reference/specification/metrics/api/#instrument-unit) of measurements reported by the metric. Enclose the string in single or double quotes if using unit annotations. |
420+
| STR | Any string to be used directly as the metric attribute value. |
421+
| BEANATTR | A non-empty string representing the MBean attribute defining the metric value. The attribute value must be a number. Special dot-notation _attributeName.itemName_ can be used to access numerical items within attributes of [CompositeType](https://docs.oracle.com/javase/8/docs/api/javax/management/openmbean/CompositeType.html). If a dot happens to be an integral part of the MBean attribute name, it must be escaped by backslash (`\`). |
422+
| BOOL | A boolean value, either `true` or `false` |
397423

398424
## Assumptions and Limitations
399425

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

+47-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import static java.util.logging.Level.FINE;
99
import static java.util.logging.Level.INFO;
1010

11+
import io.opentelemetry.instrumentation.jmx.yaml.StateMapping;
1112
import java.util.ArrayList;
1213
import java.util.List;
1314
import java.util.logging.Level;
@@ -103,7 +104,7 @@ private static void verifyAndAddNameSegment(List<String> segments, StringBuilder
103104
segments.add(newSegment);
104105
}
105106

106-
public BeanAttributeExtractor(String baseName, String... nameChain) {
107+
private BeanAttributeExtractor(String baseName, String... nameChain) {
107108
if (baseName == null || nameChain == null) {
108109
throw new IllegalArgumentException("null argument for BeanAttributeExtractor");
109110
}
@@ -287,4 +288,49 @@ private String extractStringAttribute(MBeanServerConnection connection, ObjectNa
287288
}
288289
return null;
289290
}
291+
292+
/**
293+
* Provides a bean attribute extractor to filter-out negative values by replacing them with
294+
* {@literal null}.
295+
*
296+
* @param extractor original extractor
297+
* @return equivalent extractor filtering-out negative values
298+
*/
299+
public static BeanAttributeExtractor filterNegativeValues(BeanAttributeExtractor extractor) {
300+
return new BeanAttributeExtractor(extractor.baseName, extractor.nameChain) {
301+
@Nullable
302+
@Override
303+
protected Number extractNumericalAttribute(
304+
MBeanServerConnection connection, ObjectName objectName) {
305+
Number v = super.extractNumericalAttribute(connection, objectName);
306+
if (v instanceof Long || v instanceof Integer || v instanceof Short || v instanceof Byte) {
307+
return v.longValue() < 0 ? null : v;
308+
} else if (v instanceof Double || v instanceof Float) {
309+
return v.doubleValue() < 0 ? null : v;
310+
}
311+
return v;
312+
}
313+
};
314+
}
315+
316+
public static BeanAttributeExtractor forStateMetric(
317+
BeanAttributeExtractor extractor, String key, StateMapping stateMapping) {
318+
return new BeanAttributeExtractor(extractor.baseName, extractor.nameChain) {
319+
@Override
320+
protected Object getSampleValue(MBeanServerConnection connection, ObjectName objectName) {
321+
// metric actual type is sampled in the discovery process, so we have to
322+
// make this extractor as extracting integers.
323+
return 0;
324+
}
325+
326+
@Nullable
327+
@Override
328+
protected Number extractNumericalAttribute(
329+
MBeanServerConnection connection, ObjectName objectName) {
330+
String rawStateValue = extractor.extractValue(connection, objectName);
331+
String mappedStateValue = stateMapping.getStateValue(rawStateValue);
332+
return key.equals(mappedStateValue) ? 1 : 0;
333+
}
334+
};
335+
}
290336
}

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

+19-22
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.util.Map;
2020
import java.util.Set;
2121
import javax.annotation.Nullable;
22-
import javax.management.MBeanServerConnection;
2322
import javax.management.MalformedObjectNameException;
2423
import javax.management.ObjectName;
2524

@@ -172,6 +171,14 @@ public MetricDef buildMetricDef() throws Exception {
172171
StateMapping stateMapping = getEffectiveStateMapping(m, this);
173172

174173
if (stateMapping.isEmpty()) {
174+
175+
// higher priority to metric level, then jmx rule as fallback
176+
boolean dropNegative = getEffectiveDropNegativeValues(m, this);
177+
178+
if (dropNegative) {
179+
attrExtractor = BeanAttributeExtractor.filterNegativeValues(attrExtractor);
180+
}
181+
175182
metricExtractors.add(new MetricExtractor(attrExtractor, metricInfo, attributeList));
176183
} else {
177184

@@ -206,25 +213,8 @@ private static List<MetricExtractor> createStateMappingExtractors(
206213
}
207214
}
208215

209-
BeanAttributeExtractor stateMetricExtractor =
210-
new BeanAttributeExtractor(attrExtractor.getAttributeName()) {
211-
212-
@Override
213-
protected Object getSampleValue(
214-
MBeanServerConnection connection, ObjectName objectName) {
215-
// metric actual type is sampled in the discovery process, so we have to
216-
// make this extractor as extracting integers.
217-
return 0;
218-
}
219-
220-
@Override
221-
protected Number extractNumericalAttribute(
222-
MBeanServerConnection connection, ObjectName objectName) {
223-
String rawStateValue = attrExtractor.extractValue(connection, objectName);
224-
String mappedStateValue = stateMapping.getStateValue(rawStateValue);
225-
return key.equals(mappedStateValue) ? 1 : 0;
226-
}
227-
};
216+
BeanAttributeExtractor extractor =
217+
BeanAttributeExtractor.forStateMetric(attrExtractor, key, stateMapping);
228218

229219
// state metric are always up/down counters
230220
MetricInfo stateMetricInfo =
@@ -235,8 +225,7 @@ protected Number extractNumericalAttribute(
235225
metricInfo.getUnit(),
236226
MetricInfo.Type.UPDOWNCOUNTER);
237227

238-
extractors.add(
239-
new MetricExtractor(stateMetricExtractor, stateMetricInfo, stateMetricAttributes));
228+
extractors.add(new MetricExtractor(extractor, stateMetricInfo, stateMetricAttributes));
240229
}
241230
return extractors;
242231
}
@@ -267,4 +256,12 @@ private static StateMapping getEffectiveStateMapping(Metric m, JmxRule rule) {
267256
return m.getStateMapping();
268257
}
269258
}
259+
260+
private static boolean getEffectiveDropNegativeValues(Metric m, JmxRule rule) {
261+
if (m == null || m.getDropNegativeValues() == null) {
262+
return Boolean.TRUE.equals(rule.getDropNegativeValues());
263+
} else {
264+
return Boolean.TRUE.equals(m.getDropNegativeValues());
265+
}
266+
}
270267
}

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

+11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.List;
1414
import java.util.Locale;
1515
import java.util.Map;
16+
import javax.annotation.Nullable;
1617

1718
/**
1819
* An abstract class containing skeletal info about Metrics:
@@ -48,6 +49,7 @@ abstract class MetricStructure {
4849
private static final String STATE_MAPPING_DEFAULT = "*";
4950
private String sourceUnit;
5051
private String unit;
52+
@Nullable private Boolean dropNegativeValues;
5153

5254
private MetricInfo.Type metricType;
5355
private List<MetricAttribute> metricAttributes;
@@ -76,6 +78,15 @@ public void setUnit(String unit) {
7678
this.unit = validateUnit(unit.trim());
7779
}
7880

81+
public void setDropNegativeValues(Boolean dropNegativeValues) {
82+
this.dropNegativeValues = dropNegativeValues;
83+
}
84+
85+
@Nullable
86+
public Boolean getDropNegativeValues() {
87+
return dropNegativeValues;
88+
}
89+
7990
private static void addMappedValue(
8091
StateMapping.Builder builder, String stateValue, String stateKey) {
8192
if (stateValue.equals(STATE_MAPPING_DEFAULT)) {

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

+3
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ private static void parseMetricStructure(
146146
if (sourceUnit != null) {
147147
out.setSourceUnit(sourceUnit);
148148
}
149+
150+
Boolean dropNegativeValues = (Boolean) metricStructureYaml.remove("dropNegativeValues");
151+
out.setDropNegativeValues(dropNegativeValues);
149152
}
150153

151154
private static void failOnExtraKeys(Map<String, Object> yaml) {

0 commit comments

Comments
 (0)