Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jmx filter negative values in yaml #13589

Merged
merged 7 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 43 additions & 17 deletions instrumentation/jmx-metrics/javaagent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,10 +320,10 @@ If such a conversion is not available, then an error is reported during JMX metr
Currently available unit conversions:

| `sourceUnit` | `unit` |
|--------------|-------|
| ms | s |
| us | s |
| ns | s |
|--------------|--------|
| ms | s |
| us | s |
| ns | s |

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

### Filtering negative values

Sometimes a negative value is returned by the MBean implementation when a metric is not available or not supported.
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.

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.

```yaml
rules:
- bean: java.lang:type=OperatingSystem
# can also be set at rule-level (with lower priority)
dropNegativeValues: false
mapping:
# jvm.cpu.recent_utilization
ProcessCpuLoad:
metric: jvm.cpu.recent_utilization
type: gauge
unit: '1'
# setting dropNegativeValues at metric level has priority over rule level.
dropNegativeValues: true
desc: Recent CPU utilization for the process as reported by the JVM.
```

### General Syntax

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

The following table explains the used terms with more details.

| Syntactic Element | Description |
| ---------------- | --------------- |
| 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-)). |
| ATTRIBUTE | Any well-formed string that can be used as a metric [attribute](https://opentelemetry.io/docs/reference/specification/common/#attribute) key. |
| 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. |
| 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. |
| METRIC_NAME_PREFIX | Any non-empty string which will be prepended to the specified metric (instrument) names. |
| 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). |
| TYPE | One of `counter`, `updowncounter`, or `gauge`. The default is `gauge`. This value is case insensitive. |
| 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. |
| 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. |
| STR | Any string to be used directly as the metric attribute value. |
| 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 (`\`). |
| Syntactic Element | Description |
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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-)). |
| ATTRIBUTE | Any well-formed string that can be used as a metric [attribute](https://opentelemetry.io/docs/reference/specification/common/#attribute) key. |
| 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. |
| 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. |
| METRIC_NAME_PREFIX | Any non-empty string which will be prepended to the specified metric (instrument) names. |
| 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). |
| TYPE | One of `counter`, `updowncounter`, or `gauge`. The default is `gauge`. This value is case insensitive. |
| 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. |
| 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. |
| STR | Any string to be used directly as the metric attribute value. |
| 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 (`\`). |
| BOOL | A boolean value, either `true` or `false` |

## Assumptions and Limitations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.INFO;

import io.opentelemetry.instrumentation.jmx.yaml.StateMapping;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
Expand Down Expand Up @@ -103,7 +104,7 @@ private static void verifyAndAddNameSegment(List<String> segments, StringBuilder
segments.add(newSegment);
}

public BeanAttributeExtractor(String baseName, String... nameChain) {
private BeanAttributeExtractor(String baseName, String... nameChain) {
if (baseName == null || nameChain == null) {
throw new IllegalArgumentException("null argument for BeanAttributeExtractor");
}
Expand Down Expand Up @@ -287,4 +288,49 @@ private String extractStringAttribute(MBeanServerConnection connection, ObjectNa
}
return null;
}

/**
* Provides a bean attribute extractor to filter-out negative values by replacing them with
* {@literal null}.
*
* @param extractor original extractor
* @return equivalent extractor filtering-out negative values
*/
public static BeanAttributeExtractor filterNegativeValues(BeanAttributeExtractor extractor) {
return new BeanAttributeExtractor(extractor.baseName, extractor.nameChain) {
@Nullable
@Override
protected Number extractNumericalAttribute(
MBeanServerConnection connection, ObjectName objectName) {
Number v = super.extractNumericalAttribute(connection, objectName);
if (v instanceof Long || v instanceof Integer || v instanceof Short || v instanceof Byte) {
return v.longValue() < 0 ? null : v;
} else if (v instanceof Double || v instanceof Float) {
return v.doubleValue() < 0 ? null : v;
}
return v;
}
};
}

public static BeanAttributeExtractor forStateMetric(
BeanAttributeExtractor extractor, String key, StateMapping stateMapping) {
return new BeanAttributeExtractor(extractor.baseName, extractor.nameChain) {
@Override
protected Object getSampleValue(MBeanServerConnection connection, ObjectName objectName) {
// metric actual type is sampled in the discovery process, so we have to
// make this extractor as extracting integers.
return 0;
}

@Nullable
@Override
protected Number extractNumericalAttribute(
MBeanServerConnection connection, ObjectName objectName) {
String rawStateValue = extractor.extractValue(connection, objectName);
String mappedStateValue = stateMapping.getStateValue(rawStateValue);
return key.equals(mappedStateValue) ? 1 : 0;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

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

if (stateMapping.isEmpty()) {

// higher priority to metric level, then jmx rule as fallback
boolean dropNegative = getEffectiveDropNegativeValues(m, this);

if (dropNegative) {
attrExtractor = BeanAttributeExtractor.filterNegativeValues(attrExtractor);
}

metricExtractors.add(new MetricExtractor(attrExtractor, metricInfo, attributeList));
} else {

Expand Down Expand Up @@ -206,25 +213,8 @@ private static List<MetricExtractor> createStateMappingExtractors(
}
}

BeanAttributeExtractor stateMetricExtractor =
new BeanAttributeExtractor(attrExtractor.getAttributeName()) {

@Override
protected Object getSampleValue(
MBeanServerConnection connection, ObjectName objectName) {
// metric actual type is sampled in the discovery process, so we have to
// make this extractor as extracting integers.
return 0;
}

@Override
protected Number extractNumericalAttribute(
MBeanServerConnection connection, ObjectName objectName) {
String rawStateValue = attrExtractor.extractValue(connection, objectName);
String mappedStateValue = stateMapping.getStateValue(rawStateValue);
return key.equals(mappedStateValue) ? 1 : 0;
}
};
BeanAttributeExtractor extractor =
BeanAttributeExtractor.forStateMetric(attrExtractor, key, stateMapping);

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

extractors.add(
new MetricExtractor(stateMetricExtractor, stateMetricInfo, stateMetricAttributes));
extractors.add(new MetricExtractor(extractor, stateMetricInfo, stateMetricAttributes));
}
return extractors;
}
Expand Down Expand Up @@ -267,4 +256,12 @@ private static StateMapping getEffectiveStateMapping(Metric m, JmxRule rule) {
return m.getStateMapping();
}
}

private static boolean getEffectiveDropNegativeValues(Metric m, JmxRule rule) {
if (m == null || m.getDropNegativeValues() == null) {
return Boolean.TRUE.equals(rule.getDropNegativeValues());
} else {
return Boolean.TRUE.equals(m.getDropNegativeValues());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;

/**
* An abstract class containing skeletal info about Metrics:
Expand Down Expand Up @@ -48,6 +49,7 @@ abstract class MetricStructure {
private static final String STATE_MAPPING_DEFAULT = "*";
private String sourceUnit;
private String unit;
@Nullable private Boolean dropNegativeValues;

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

public void setDropNegativeValues(Boolean dropNegativeValues) {
this.dropNegativeValues = dropNegativeValues;
}

@Nullable
public Boolean getDropNegativeValues() {
return dropNegativeValues;
}

private static void addMappedValue(
StateMapping.Builder builder, String stateValue, String stateKey) {
if (stateValue.equals(STATE_MAPPING_DEFAULT)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ private static void parseMetricStructure(
if (sourceUnit != null) {
out.setSourceUnit(sourceUnit);
}

Boolean dropNegativeValues = (Boolean) metricStructureYaml.remove("dropNegativeValues");
out.setDropNegativeValues(dropNegativeValues);
}

private static void failOnExtraKeys(Map<String, Object> yaml) {
Expand Down
Loading
Loading