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

Support for exposing atttributes as labels #996

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
57 changes: 54 additions & 3 deletions collector/src/main/java/io/prometheus/jmx/JmxCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ static class Rule {
ArrayList<String> labelValues;
}

public static class MetricCustomizer {
MBeanFilter mbeanFilter;
List<String> attributesAsLabels;
}

public static class MBeanFilter {
String domain;
Map<String, String> properties;
}

private static class Config {
Integer startDelaySeconds = 0;
String jmxUrl = "";
Expand All @@ -86,7 +96,7 @@ private static class Config {
ObjectNameAttributeFilter objectNameAttributeFilter;
List<Rule> rules = new ArrayList<>();
long lastUpdate = 0L;

List<MetricCustomizer> metricCustomizers = new ArrayList<>();
MatchedRulesCache rulesCache;
}

Expand Down Expand Up @@ -296,6 +306,29 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
}
}

if (yamlConfig.containsKey("metricCustomizers")) {
List<Map<String, Object>> metricCustomizersYaml =
(List<Map<String, Object>>) yamlConfig.get("metricCustomizers");
for (Map<String, Object> metricCustomizerYaml : metricCustomizersYaml) {
Map<String, Object> mbeanFilterYaml =
(Map<String, Object>) metricCustomizerYaml.get("mbeanFilter");
MBeanFilter mbeanFilter = new MBeanFilter();
mbeanFilter.domain = (String) mbeanFilterYaml.get("domain");
mbeanFilter.properties = (Map<String, String>) mbeanFilterYaml.get("properties");

List<String> attributesAsLabels =
(List<String>) metricCustomizerYaml.get("attributesAsLabels");
if (attributesAsLabels == null) {
attributesAsLabels = new ArrayList<>();
}

MetricCustomizer metricCustomizer = new MetricCustomizer();
metricCustomizer.mbeanFilter = mbeanFilter;
metricCustomizer.attributesAsLabels = attributesAsLabels;
cfg.metricCustomizers.add(metricCustomizer);
}
}

if (yamlConfig.containsKey("rules")) {
List<Map<String, Object>> configRules =
(List<Map<String, Object>>) yamlConfig.get("rules");
Expand Down Expand Up @@ -469,7 +502,8 @@ private MatchedRule defaultExport(
String help,
Double value,
double valueFactor,
String type) {
String type,
Map<String, String> attributesAsLabelsWithValues) {
StringBuilder name = new StringBuilder();
name.append(domain);
if (beanProperties.size() > 0) {
Expand Down Expand Up @@ -504,6 +538,7 @@ private MatchedRule defaultExport(
labelValues.add(entry.getValue());
}
}
addAttributesAsLabelsWithValuesToLabels(config, attributesAsLabelsWithValues, labelNames, labelValues);

return new MatchedRule(
fullname, matchName, type, help, labelNames, labelValues, value, valueFactor);
Expand All @@ -512,6 +547,7 @@ private MatchedRule defaultExport(
public void recordBean(
String domain,
LinkedHashMap<String, String> beanProperties,
Map<String, String> attributesAsLabelsWithValues,
LinkedList<String> attrKeys,
String attrName,
String attrType,
Expand Down Expand Up @@ -609,7 +645,8 @@ public void recordBean(
help,
value,
rule.valueFactor,
rule.type);
rule.type,
attributesAsLabelsWithValues);
addToCache(rule, matchName, matchedRule);
break;
}
Expand All @@ -631,6 +668,7 @@ public void recordBean(
// Set the labels.
ArrayList<String> labelNames = new ArrayList<>();
ArrayList<String> labelValues = new ArrayList<>();
addAttributesAsLabelsWithValuesToLabels(config, attributesAsLabelsWithValues, labelNames, labelValues);
if (rule.labelNames != null) {
for (int i = 0; i < rule.labelNames.size(); i++) {
final String unsafeLabelName = rule.labelNames.get(i);
Expand Down Expand Up @@ -705,6 +743,18 @@ public void recordBean(
}
}

private static void addAttributesAsLabelsWithValuesToLabels(Config config, Map<String, String> attributesAsLabelsWithValues, List<String> labelNames, List<String> labelValues) {
attributesAsLabelsWithValues.forEach(
(attributeAsLabelName, attributeValue) -> {
String labelName = safeName(attributeAsLabelName);
if (config.lowercaseOutputLabelNames) {
labelName = labelName.toLowerCase();
}
labelNames.add(labelName);
labelValues.add(attributeValue);
});
}

@Override
public MetricSnapshots collect() {
// Take a reference to the current config and collect with this one
Expand All @@ -725,6 +775,7 @@ public MetricSnapshots collect() {
config.includeObjectNames,
config.excludeObjectNames,
config.objectNameAttributeFilter,
config.metricCustomizers,
receiver,
jmxMBeanPropertyCache);

Expand Down
72 changes: 69 additions & 3 deletions collector/src/main/java/io/prometheus/jmx/JmxScraper.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,24 @@
import io.prometheus.jmx.logger.LoggerFactory;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.*;
import javax.management.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServerConnection;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeType;
import javax.management.openmbean.TabularData;
Expand All @@ -43,6 +59,7 @@ public interface MBeanReceiver {
void recordBean(
String domain,
LinkedHashMap<String, String> beanProperties,
Map<String, String> attributesAsLabelsWithValues,
LinkedList<String> attrKeys,
String attrName,
String attrType,
Expand All @@ -56,6 +73,7 @@ void recordBean(
private final String password;
private final boolean ssl;
private final List<ObjectName> includeObjectNames, excludeObjectNames;
private final List<JmxCollector.MetricCustomizer> metricCustomizers;
private final ObjectNameAttributeFilter objectNameAttributeFilter;
private final JmxMBeanPropertyCache jmxMBeanPropertyCache;

Expand All @@ -67,6 +85,7 @@ public JmxScraper(
List<ObjectName> includeObjectNames,
List<ObjectName> excludeObjectNames,
ObjectNameAttributeFilter objectNameAttributeFilter,
List<JmxCollector.MetricCustomizer> metricCustomizers,
MBeanReceiver receiver,
JmxMBeanPropertyCache jmxMBeanPropertyCache) {
this.jmxUrl = jmxUrl;
Expand All @@ -76,6 +95,7 @@ public JmxScraper(
this.ssl = ssl;
this.includeObjectNames = includeObjectNames;
this.excludeObjectNames = excludeObjectNames;
this.metricCustomizers = metricCustomizers;
this.objectNameAttributeFilter = objectNameAttributeFilter;
this.jmxMBeanPropertyCache = jmxMBeanPropertyCache;
}
Expand Down Expand Up @@ -204,6 +224,12 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {

final String mBeanNameString = mBeanName.toString();
final String mBeanDomain = mBeanName.getDomain();
JmxCollector.MetricCustomizer metricCustomizer = getMetricCustomizer(mBeanName);
Map<String, String> attributesAsLabelsWithValues = new HashMap<>();
if (metricCustomizer != null) {
attributesAsLabelsWithValues =
getAttributesAsLabelsWithValues(metricCustomizer, attributes);
}

for (Object object : attributes) {
// The contents of an AttributeList should all be Attribute instances, but we'll verify
Expand Down Expand Up @@ -231,6 +257,7 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
mBeanName,
mBeanDomain,
jmxMBeanPropertyCache.getKeyPropertyList(mBeanName),
attributesAsLabelsWithValues,
new LinkedList<>(),
mBeanAttributeInfo.getName(),
mBeanAttributeInfo.getType(),
Expand All @@ -251,6 +278,35 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
}
}

private Map<String, String> getAttributesAsLabelsWithValues(JmxCollector.MetricCustomizer metricCustomizer, AttributeList attributes) {
Map<String, Object> attributeMap = attributes.asList().stream()
.collect(Collectors.toMap(Attribute::getName, Attribute::getValue));
Map<String, String> attributesAsLabelsWithValues = new HashMap<>();
for (String attributeAsLabel : metricCustomizer.attributesAsLabels) {
Object attrValue = attributeMap.get(attributeAsLabel);
if (attrValue != null) {
attributesAsLabelsWithValues.put(attributeAsLabel, attrValue.toString());
}
}
return attributesAsLabelsWithValues;
}

private JmxCollector.MetricCustomizer getMetricCustomizer(ObjectName mBeanName) {
if (!metricCustomizers.isEmpty()) {
for (JmxCollector.MetricCustomizer metricCustomizer : metricCustomizers) {
if (filterMbeanByDomainAndProperties(mBeanName, metricCustomizer)) {
return metricCustomizer;
}
}
}
return null;
}

private boolean filterMbeanByDomainAndProperties(ObjectName mBeanName, JmxCollector.MetricCustomizer metricCustomizer) {
return metricCustomizer.mbeanFilter.domain.equals(mBeanName.getDomain()) &&
mBeanName.getKeyPropertyList().entrySet().containsAll(metricCustomizer.mbeanFilter.properties.entrySet());
}

private void processAttributesOneByOne(
MBeanServerConnection beanConn,
ObjectName mbeanName,
Expand All @@ -269,6 +325,7 @@ private void processAttributesOneByOne(
mbeanName,
mbeanName.getDomain(),
jmxMBeanPropertyCache.getKeyPropertyList(mbeanName),
new HashMap<>(),
new LinkedList<>(),
attr.getName(),
attr.getType(),
Expand All @@ -286,6 +343,7 @@ private void processBeanValue(
ObjectName objectName,
String domain,
LinkedHashMap<String, String> beanProperties,
Map<String, String> attributesAsLabelsWithValues,
LinkedList<String> attrKeys,
String attrName,
String attrType,
Expand All @@ -303,7 +361,7 @@ private void processBeanValue(
}
LOGGER.log(FINE, "%s%s%s scrape: %s", domain, beanProperties, attrName, value);
this.receiver.recordBean(
domain, beanProperties, attrKeys, attrName, attrType, attrDescription, value);
domain, beanProperties, attributesAsLabelsWithValues, attrKeys, attrName, attrType, attrDescription, value);
} else if (value instanceof CompositeData) {
LOGGER.log(FINE, "%s%s%s scrape: compositedata", domain, beanProperties, attrName);
CompositeData composite = (CompositeData) value;
Expand All @@ -317,6 +375,7 @@ private void processBeanValue(
objectName,
domain,
beanProperties,
attributesAsLabelsWithValues,
attrKeys,
key,
typ,
Expand Down Expand Up @@ -383,6 +442,7 @@ private void processBeanValue(
objectName,
domain,
l2s,
attributesAsLabelsWithValues,
attrNames,
name,
typ,
Expand All @@ -403,6 +463,7 @@ private void processBeanValue(
objectName,
domain,
beanProperties,
attributesAsLabelsWithValues,
attrKeys,
attrName,
attrType,
Expand All @@ -415,6 +476,7 @@ private void processBeanValue(
objectName,
domain,
beanProperties,
attributesAsLabelsWithValues,
attrKeys,
attrName,
attrType,
Expand All @@ -430,6 +492,7 @@ private static class StdoutWriter implements MBeanReceiver {
public void recordBean(
String domain,
LinkedHashMap<String, String> beanProperties,
Map<String, String> attributesAsLabelsWithValues,
LinkedList<String> attrKeys,
String attrName,
String attrType,
Expand All @@ -454,6 +517,7 @@ public static void main(String[] args) throws Exception {
objectNames,
new LinkedList<>(),
objectNameAttributeFilter,
new LinkedList<>(),
new StdoutWriter(),
new JmxMBeanPropertyCache())
.doScrape();
Expand All @@ -466,6 +530,7 @@ public static void main(String[] args) throws Exception {
objectNames,
new LinkedList<>(),
objectNameAttributeFilter,
new LinkedList<>(),
new StdoutWriter(),
new JmxMBeanPropertyCache())
.doScrape();
Expand All @@ -478,6 +543,7 @@ public static void main(String[] args) throws Exception {
objectNames,
new LinkedList<>(),
objectNameAttributeFilter,
new LinkedList<>(),
new StdoutWriter(),
new JmxMBeanPropertyCache())
.doScrape();
Expand Down
13 changes: 13 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ excludeObjectNameAttributes:
"java.lang:type=Runtime":
- "ClassPath"
- "SystemProperties"
metricCustomizers:
- mbeanFilter:
domain: org.apache.cassandra.metrics
properties:
type: <type>
attributesAsLabels:
- string1
- string2
rules:
- pattern: 'org.apache.cassandra.metrics<type=(\w+), name=(\w+)><>Value: (\d+)'
name: cassandra_$1_$2
Expand All @@ -119,6 +127,7 @@ rules:
cache: false
type: GAUGE
attrNameSnakeCase: false

```
Name | Description
---------|------------
Expand All @@ -144,6 +153,10 @@ labels | A map of label name to label value pairs. Capture groups fro
help | Help text for the metric. Capture groups from `pattern` can be used. `name` must be set to use this. Defaults to the mBean attribute description, domain, and name of the attribute.
cache | Whether to cache bean name expressions to rule computation (match and mismatch). Not recommended for rules matching on bean value, as only the value from the first scrape will be cached and re-used. This can increase performance when collecting a lot of mbeans. Defaults to `false`.
type | The type of the metric, can be `GAUGE`, `COUNTER` or `UNTYPED`. `name` must be set to use this. Defaults to `UNTYPED`.
metricCustomizers | A list of objects that contain `mbeanFilter` and `attributesAsLabels`. For those mbeans that match the filter, the items in the `attributesAsLabels` list will be added as attributes to the existing metrics.
domain | Domain of an mbean.
properties | Properties of an mbean.
attributesAsLabels | List of elements to be added as attributes to existing metrics.

Metric names and label names are sanitized. All characters other than `[a-zA-Z0-9:_]` are replaced with underscores,
and adjacent underscores are collapsed. There's no limitations on label values or the help text.
Expand Down