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 attributes as labels #996

Merged
merged 4 commits into from
Feb 18, 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
70 changes: 67 additions & 3 deletions collector/src/main/java/io/prometheus/jmx/JmxCollector.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,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 @@ -90,7 +100,7 @@ private static class Config {
ObjectNameAttributeFilter objectNameAttributeFilter;
final List<Rule> rules = new ArrayList<>();
long lastUpdate = 0L;

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

Expand Down Expand Up @@ -326,6 +336,42 @@ 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");
if (metricCustomizersYaml != null) {
for (Map<String, Object> metricCustomizerYaml : metricCustomizersYaml) {
Map<String, Object> mbeanFilterYaml =
(Map<String, Object>) metricCustomizerYaml.get("mbeanFilter");
if (mbeanFilterYaml == null) {
throw new IllegalArgumentException(
"Must provide mbeanFilter, if metricCustomizers is given: " + metricCustomizersYaml);
}
MBeanFilter mbeanFilter = new MBeanFilter();
mbeanFilter.domain = (String) mbeanFilterYaml.get("domain");
if (mbeanFilter.domain == null) {
throw new IllegalArgumentException(
"Must provide domain, if metricCustomizers is given: " + metricCustomizersYaml);
}
mbeanFilter.properties = (Map<String, String>) mbeanFilterYaml.getOrDefault("properties", new HashMap<>());

List<String> attributesAsLabels =
(List<String>) metricCustomizerYaml.get("attributesAsLabels");
if (attributesAsLabels == null) {
throw new IllegalArgumentException(
"Must provide attributesAsLabels, if metricCustomizers is given: " + metricCustomizersYaml);
}
MetricCustomizer metricCustomizer = new MetricCustomizer();
metricCustomizer.mbeanFilter = mbeanFilter;
metricCustomizer.attributesAsLabels = attributesAsLabels;
cfg.metricCustomizers.add(metricCustomizer);
}
} else {
throw new IllegalArgumentException(
"Must provide mbeanFilter, if metricCustomizers is given ");
}
}

if (yamlConfig.containsKey("rules")) {
List<Map<String, Object>> configRules =
(List<Map<String, Object>>) yamlConfig.get("rules");
Expand Down Expand Up @@ -498,7 +544,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.isEmpty()) {
Expand Down Expand Up @@ -533,6 +580,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 @@ -541,6 +589,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 @@ -638,7 +687,8 @@ public void recordBean(
help,
value,
rule.valueFactor,
rule.type);
rule.type,
attributesAsLabelsWithValues);
addToCache(rule, matchName, matchedRule);
break;
}
Expand All @@ -660,6 +710,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 @@ -734,6 +785,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 @@ -754,6 +817,7 @@ public MetricSnapshots collect() {
config.includeObjectNames,
config.excludeObjectNames,
config.objectNameAttributeFilter,
config.metricCustomizers,
receiver,
jmxMBeanPropertyCache);

Expand Down
54 changes: 53 additions & 1 deletion collector/src/main/java/io/prometheus/jmx/JmxScraper.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.Collections;
import java.util.stream.Collectors;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.JMException;
Expand Down Expand Up @@ -72,6 +74,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 @@ -85,6 +88,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 @@ -109,6 +113,7 @@ public JmxScraper(
List<ObjectName> includeObjectNames,
List<ObjectName> excludeObjectNames,
ObjectNameAttributeFilter objectNameAttributeFilter,
List<JmxCollector.MetricCustomizer> metricCustomizers,
MBeanReceiver receiver,
JmxMBeanPropertyCache jmxMBeanPropertyCache) {
this.jmxUrl = jmxUrl;
Expand All @@ -118,6 +123,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 @@ -253,6 +259,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 = Collections.emptyMap();
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 @@ -280,6 +292,7 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
mBeanName,
mBeanDomain,
jmxMBeanPropertyCache.getKeyPropertyList(mBeanName),
attributesAsLabelsWithValues,
new LinkedList<>(),
mBeanAttributeInfo.getName(),
mBeanAttributeInfo.getType(),
Expand All @@ -300,6 +313,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 @@ -318,6 +360,7 @@ private void processAttributesOneByOne(
mbeanName,
mbeanName.getDomain(),
jmxMBeanPropertyCache.getKeyPropertyList(mbeanName),
new HashMap<>(),
new LinkedList<>(),
attr.getName(),
attr.getType(),
Expand All @@ -335,6 +378,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 @@ -352,7 +396,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 @@ -366,6 +410,7 @@ private void processBeanValue(
objectName,
domain,
beanProperties,
attributesAsLabelsWithValues,
attrKeys,
key,
typ,
Expand Down Expand Up @@ -432,6 +477,7 @@ private void processBeanValue(
objectName,
domain,
l2s,
attributesAsLabelsWithValues,
attrNames,
name,
typ,
Expand All @@ -452,6 +498,7 @@ private void processBeanValue(
objectName,
domain,
beanProperties,
attributesAsLabelsWithValues,
attrKeys,
attrName,
attrType,
Expand All @@ -464,6 +511,7 @@ private void processBeanValue(
objectName,
domain,
beanProperties,
attributesAsLabelsWithValues,
attrKeys,
attrName,
attrType,
Expand All @@ -479,6 +527,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 @@ -503,6 +552,7 @@ public static void main(String[] args) throws Exception {
objectNames,
new LinkedList<>(),
objectNameAttributeFilter,
new LinkedList<>(),
new StdoutWriter(),
new JmxMBeanPropertyCache())
.doScrape();
Expand All @@ -515,6 +565,7 @@ public static void main(String[] args) throws Exception {
objectNames,
new LinkedList<>(),
objectNameAttributeFilter,
new LinkedList<>(),
new StdoutWriter(),
new JmxMBeanPropertyCache())
.doScrape();
Expand All @@ -527,6 +578,7 @@ public static void main(String[] args) throws Exception {
objectNames,
new LinkedList<>(),
objectNameAttributeFilter,
new LinkedList<>(),
new StdoutWriter(),
new JmxMBeanPropertyCache())
.doScrape();
Expand Down
58 changes: 58 additions & 0 deletions collector/src/test/java/io/prometheus/jmx/CustomValueMBean.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (C) 2023-present The Prometheus jmx_exporter Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.prometheus.jmx;

import javax.management.MBeanServer;
import javax.management.ObjectName;

/** Class to implement CustomValueMBean */
public interface CustomValueMBean {

/**
* Method to get the value
*
* @return value
*/
Integer getValue();

/**
* Method to get the text
*
* @return text
*/
String getText();
}

/** Class to implement CustomValue */
class CustomValue implements CustomValueMBean {

@Override
public Integer getValue() {
return 345;
}

@Override
public String getText() {
return "value";
}

public static void registerBean(MBeanServer mbs) throws javax.management.JMException {
ObjectName mbeanName =
new ObjectName("io.prometheus.jmx:type=customValue");
CustomValueMBean mbean = new CustomValue();
mbs.registerMBean(mbean, mbeanName);
}
}
Loading