From d60a82826d983acfcf713ae446366ba3435c7785 Mon Sep 17 00:00:00 2001 From: Martin Bickel Date: Wed, 28 Aug 2024 17:10:19 +0200 Subject: [PATCH] Support exposing atttributes as labels Co-authored-by: Karina Calma Signed-off-by: Martin Bickel --- .../java/io/prometheus/jmx/JmxCollector.java | 30 ++++++++- .../java/io/prometheus/jmx/JmxScraper.java | 61 ++++++++++++++++++- docs/README.md | 5 ++ 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/collector/src/main/java/io/prometheus/jmx/JmxCollector.java b/collector/src/main/java/io/prometheus/jmx/JmxCollector.java index 450083da..23820335 100644 --- a/collector/src/main/java/io/prometheus/jmx/JmxCollector.java +++ b/collector/src/main/java/io/prometheus/jmx/JmxCollector.java @@ -71,6 +71,7 @@ static class Rule { String type = "UNKNOWN"; ArrayList labelNames; ArrayList labelValues; + ArrayList attributesAsLabels; } private static class Config { @@ -348,6 +349,13 @@ private Config loadConfig(Map yamlConfig) throws MalformedObject } } + if (yamlRule.containsKey("attributesAsLabels")) { + List attributes = (List) yamlRule.get("attributesAsLabels"); + rule.attributesAsLabels = new ArrayList<>(); + if (attributes != null) { + rule.attributesAsLabels.addAll(attributes); + } + } // Validation. if ((rule.labelNames != null || rule.help != null) && rule.name == null) { throw new IllegalArgumentException( @@ -469,7 +477,8 @@ private MatchedRule defaultExport( String help, Double value, double valueFactor, - String type) { + String type, + Map attributesAsLabelsWithValues) { StringBuilder name = new StringBuilder(); name.append(domain); if (beanProperties.size() > 0) { @@ -504,6 +513,7 @@ private MatchedRule defaultExport( labelValues.add(entry.getValue()); } } + addAttributesAsLabelsWithValuesToLabels(config, attributesAsLabelsWithValues, labelNames, labelValues); return new MatchedRule( fullname, matchName, type, help, labelNames, labelValues, value, valueFactor); @@ -512,6 +522,7 @@ private MatchedRule defaultExport( public void recordBean( String domain, LinkedHashMap beanProperties, + Map attributesAsLabelsWithValues, LinkedList attrKeys, String attrName, String attrType, @@ -609,7 +620,8 @@ public void recordBean( help, value, rule.valueFactor, - rule.type); + rule.type, + attributesAsLabelsWithValues); addToCache(rule, matchName, matchedRule); break; } @@ -631,6 +643,7 @@ public void recordBean( // Set the labels. ArrayList labelNames = new ArrayList<>(); ArrayList 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); @@ -705,6 +718,18 @@ public void recordBean( } } + private static void addAttributesAsLabelsWithValuesToLabels(Config config, Map attributesAsLabelsWithValues, List labelNames, List 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 @@ -725,6 +750,7 @@ public MetricSnapshots collect() { config.includeObjectNames, config.excludeObjectNames, config.objectNameAttributeFilter, + config.rules, receiver, jmxMBeanPropertyCache); diff --git a/collector/src/main/java/io/prometheus/jmx/JmxScraper.java b/collector/src/main/java/io/prometheus/jmx/JmxScraper.java index 37e5018c..b1a2c9db 100644 --- a/collector/src/main/java/io/prometheus/jmx/JmxScraper.java +++ b/collector/src/main/java/io/prometheus/jmx/JmxScraper.java @@ -31,6 +31,8 @@ import java.util.Optional; import java.util.Set; import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.stream.Collectors; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.JMException; @@ -58,6 +60,7 @@ public interface MBeanReceiver { void recordBean( String domain, LinkedHashMap beanProperties, + Map attributesAsLabelsWithValues, LinkedList attrKeys, String attrName, String attrType, @@ -71,6 +74,7 @@ void recordBean( private final String password; private final boolean ssl; private final List includeObjectNames, excludeObjectNames; + private final List rules; private final ObjectNameAttributeFilter objectNameAttributeFilter; private final JmxMBeanPropertyCache jmxMBeanPropertyCache; @@ -82,6 +86,7 @@ public JmxScraper( List includeObjectNames, List excludeObjectNames, ObjectNameAttributeFilter objectNameAttributeFilter, + List rules, MBeanReceiver receiver, JmxMBeanPropertyCache jmxMBeanPropertyCache) { this.jmxUrl = jmxUrl; @@ -91,6 +96,7 @@ public JmxScraper( this.ssl = ssl; this.includeObjectNames = includeObjectNames; this.excludeObjectNames = excludeObjectNames; + this.rules = rules; this.objectNameAttributeFilter = objectNameAttributeFilter; this.jmxMBeanPropertyCache = jmxMBeanPropertyCache; } @@ -217,6 +223,7 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) { final String mBeanNameString = mBeanName.toString(); final String mBeanDomain = mBeanName.getDomain(); + Map attributeMap = attributes.asList().stream().collect(Collectors.toMap(Attribute::getName, Attribute::getValue)); for (Object object : attributes) { // The contents of an AttributeList should all be Attribute instances, but we'll verify @@ -237,6 +244,8 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) { continue; } + Map attributesAsLabelsWithValues = getAttributesAsLabelsWithValues(mBeanName, attribute, attributeMap); + MBeanAttributeInfo mBeanAttributeInfo = name2MBeanAttributeInfo.get(attribute.getName()); LOGGER.log(FINE, "%s_%s process", mBeanName, mBeanAttributeInfo.getName()); @@ -244,6 +253,7 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) { mBeanName, mBeanDomain, jmxMBeanPropertyCache.getKeyPropertyList(mBeanName), + attributesAsLabelsWithValues, new LinkedList<>(), mBeanAttributeInfo.getName(), mBeanAttributeInfo.getType(), @@ -264,6 +274,45 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) { } } + private Map getAttributesAsLabelsWithValues(ObjectName mBeanName, Attribute attribute, Map attributeMap) { + JmxCollector.Rule matchedRule = null; + for (JmxCollector.Rule rule : rules) { + if (rule.pattern != null) { + Object matchBeanValue = rule.cache ? "" : attribute.getValue(); + List attrKeys = new LinkedList<>(); + if (attribute.getValue() instanceof TabularData || attribute.getValue() instanceof CompositeData) { + attrKeys.add(attribute.getName()); + } + String beanName = mBeanName.getDomain() + + angleBrackets(jmxMBeanPropertyCache.getKeyPropertyList(mBeanName).toString()) + + angleBrackets(attrKeys.toString()); + String matchName = beanName + attribute.getName() + ": " + matchBeanValue; + Matcher matcher = rule.pattern.matcher(matchName); + if (matcher.matches() && rule.attributesAsLabels != null) { + matchedRule = rule; + } + } else if (rule.name == null) { + matchedRule = rule; + } + } + Map attributesAsLabelsWithValues = new HashMap<>(); + if (matchedRule != null) { + for (String attributeAsLabel : matchedRule.attributesAsLabels) { + Object attrValue = attributeMap.get(attributeAsLabel); + if (attrValue != null) { + attributesAsLabelsWithValues.put( + attributeAsLabel, + attrValue.toString()); + } + } + } + return attributesAsLabelsWithValues; + } + + private String angleBrackets(String s) { + return "<" + s.substring(1, s.length() - 1) + ">"; + } + private void processAttributesOneByOne( MBeanServerConnection beanConn, ObjectName mbeanName, @@ -282,6 +331,7 @@ private void processAttributesOneByOne( mbeanName, mbeanName.getDomain(), jmxMBeanPropertyCache.getKeyPropertyList(mbeanName), + new HashMap<>(), new LinkedList<>(), attr.getName(), attr.getType(), @@ -299,6 +349,7 @@ private void processBeanValue( ObjectName objectName, String domain, LinkedHashMap beanProperties, + Map attributesAsLabelsWithValues, LinkedList attrKeys, String attrName, String attrType, @@ -316,7 +367,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; @@ -330,6 +381,7 @@ private void processBeanValue( objectName, domain, beanProperties, + attributesAsLabelsWithValues, attrKeys, key, typ, @@ -396,6 +448,7 @@ private void processBeanValue( objectName, domain, l2s, + attributesAsLabelsWithValues, attrNames, name, typ, @@ -416,6 +469,7 @@ private void processBeanValue( objectName, domain, beanProperties, + attributesAsLabelsWithValues, attrKeys, attrName, attrType, @@ -428,6 +482,7 @@ private void processBeanValue( objectName, domain, beanProperties, + attributesAsLabelsWithValues, attrKeys, attrName, attrType, @@ -443,6 +498,7 @@ private static class StdoutWriter implements MBeanReceiver { public void recordBean( String domain, LinkedHashMap beanProperties, + Map attributesAsLabelsWithValues, LinkedList attrKeys, String attrName, String attrType, @@ -467,6 +523,7 @@ public static void main(String[] args) throws Exception { objectNames, new LinkedList<>(), objectNameAttributeFilter, + new LinkedList<>(), new StdoutWriter(), new JmxMBeanPropertyCache()) .doScrape(); @@ -479,6 +536,7 @@ public static void main(String[] args) throws Exception { objectNames, new LinkedList<>(), objectNameAttributeFilter, + new LinkedList<>(), new StdoutWriter(), new JmxMBeanPropertyCache()) .doScrape(); @@ -491,6 +549,7 @@ public static void main(String[] args) throws Exception { objectNames, new LinkedList<>(), objectNameAttributeFilter, + new LinkedList<>(), new StdoutWriter(), new JmxMBeanPropertyCache()) .doScrape(); diff --git a/docs/README.md b/docs/README.md index 464553fa..76fa0521 100644 --- a/docs/README.md +++ b/docs/README.md @@ -119,6 +119,10 @@ rules: cache: false type: GAUGE attrNameSnakeCase: false + attributesAsLabels: + - string1 + - string2 + ``` Name | Description ---------|------------ @@ -144,6 +148,7 @@ 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`. +attributesAsLabels | A list of attributes from an mBean which will be added as labels for all the metrics of that mBean. Defaults to none. 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.