Skip to content

Commit 72ed97a

Browse files
Support for exposing attributes as labels (#996)
* Support exposing atttributes as labels * add integration tests for metricCustomizers * Add validation checks and extend documentation --- Signed-off-by: Martin Bickel <[email protected]> Signed-off-by: Zoltan Adasz <[email protected]> Co-authored-by: Karina Calma <[email protected]>
1 parent 1250cd4 commit 72ed97a

File tree

13 files changed

+458
-4
lines changed

13 files changed

+458
-4
lines changed

collector/src/main/java/io/prometheus/jmx/JmxCollector.java

+67-3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ static class Rule {
7777
ArrayList<String> labelValues;
7878
}
7979

80+
public static class MetricCustomizer {
81+
MBeanFilter mbeanFilter;
82+
List<String> attributesAsLabels;
83+
}
84+
85+
public static class MBeanFilter {
86+
String domain;
87+
Map<String, String> properties;
88+
}
89+
8090
private static class Config {
8191
Integer startDelaySeconds = 0;
8292
String jmxUrl = "";
@@ -90,7 +100,7 @@ private static class Config {
90100
ObjectNameAttributeFilter objectNameAttributeFilter;
91101
final List<Rule> rules = new ArrayList<>();
92102
long lastUpdate = 0L;
93-
103+
List<MetricCustomizer> metricCustomizers = new ArrayList<>();
94104
MatchedRulesCache rulesCache;
95105
}
96106

@@ -326,6 +336,42 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
326336
}
327337
}
328338

339+
if (yamlConfig.containsKey("metricCustomizers")) {
340+
List<Map<String, Object>> metricCustomizersYaml =
341+
(List<Map<String, Object>>) yamlConfig.get("metricCustomizers");
342+
if (metricCustomizersYaml != null) {
343+
for (Map<String, Object> metricCustomizerYaml : metricCustomizersYaml) {
344+
Map<String, Object> mbeanFilterYaml =
345+
(Map<String, Object>) metricCustomizerYaml.get("mbeanFilter");
346+
if (mbeanFilterYaml == null) {
347+
throw new IllegalArgumentException(
348+
"Must provide mbeanFilter, if metricCustomizers is given: " + metricCustomizersYaml);
349+
}
350+
MBeanFilter mbeanFilter = new MBeanFilter();
351+
mbeanFilter.domain = (String) mbeanFilterYaml.get("domain");
352+
if (mbeanFilter.domain == null) {
353+
throw new IllegalArgumentException(
354+
"Must provide domain, if metricCustomizers is given: " + metricCustomizersYaml);
355+
}
356+
mbeanFilter.properties = (Map<String, String>) mbeanFilterYaml.getOrDefault("properties", new HashMap<>());
357+
358+
List<String> attributesAsLabels =
359+
(List<String>) metricCustomizerYaml.get("attributesAsLabels");
360+
if (attributesAsLabels == null) {
361+
throw new IllegalArgumentException(
362+
"Must provide attributesAsLabels, if metricCustomizers is given: " + metricCustomizersYaml);
363+
}
364+
MetricCustomizer metricCustomizer = new MetricCustomizer();
365+
metricCustomizer.mbeanFilter = mbeanFilter;
366+
metricCustomizer.attributesAsLabels = attributesAsLabels;
367+
cfg.metricCustomizers.add(metricCustomizer);
368+
}
369+
} else {
370+
throw new IllegalArgumentException(
371+
"Must provide mbeanFilter, if metricCustomizers is given ");
372+
}
373+
}
374+
329375
if (yamlConfig.containsKey("rules")) {
330376
List<Map<String, Object>> configRules =
331377
(List<Map<String, Object>>) yamlConfig.get("rules");
@@ -498,7 +544,8 @@ private MatchedRule defaultExport(
498544
String help,
499545
Double value,
500546
double valueFactor,
501-
String type) {
547+
String type,
548+
Map<String, String> attributesAsLabelsWithValues) {
502549
StringBuilder name = new StringBuilder();
503550
name.append(domain);
504551
if (!beanProperties.isEmpty()) {
@@ -533,6 +580,7 @@ private MatchedRule defaultExport(
533580
labelValues.add(entry.getValue());
534581
}
535582
}
583+
addAttributesAsLabelsWithValuesToLabels(config, attributesAsLabelsWithValues, labelNames, labelValues);
536584

537585
return new MatchedRule(
538586
fullname, matchName, type, help, labelNames, labelValues, value, valueFactor);
@@ -541,6 +589,7 @@ private MatchedRule defaultExport(
541589
public void recordBean(
542590
String domain,
543591
LinkedHashMap<String, String> beanProperties,
592+
Map<String, String> attributesAsLabelsWithValues,
544593
LinkedList<String> attrKeys,
545594
String attrName,
546595
String attrType,
@@ -638,7 +687,8 @@ public void recordBean(
638687
help,
639688
value,
640689
rule.valueFactor,
641-
rule.type);
690+
rule.type,
691+
attributesAsLabelsWithValues);
642692
addToCache(rule, matchName, matchedRule);
643693
break;
644694
}
@@ -660,6 +710,7 @@ public void recordBean(
660710
// Set the labels.
661711
ArrayList<String> labelNames = new ArrayList<>();
662712
ArrayList<String> labelValues = new ArrayList<>();
713+
addAttributesAsLabelsWithValuesToLabels(config, attributesAsLabelsWithValues, labelNames, labelValues);
663714
if (rule.labelNames != null) {
664715
for (int i = 0; i < rule.labelNames.size(); i++) {
665716
final String unsafeLabelName = rule.labelNames.get(i);
@@ -734,6 +785,18 @@ public void recordBean(
734785
}
735786
}
736787

788+
private static void addAttributesAsLabelsWithValuesToLabels(Config config, Map<String, String> attributesAsLabelsWithValues, List<String> labelNames, List<String> labelValues) {
789+
attributesAsLabelsWithValues.forEach(
790+
(attributeAsLabelName, attributeValue) -> {
791+
String labelName = safeName(attributeAsLabelName);
792+
if (config.lowercaseOutputLabelNames) {
793+
labelName = labelName.toLowerCase();
794+
}
795+
labelNames.add(labelName);
796+
labelValues.add(attributeValue);
797+
});
798+
}
799+
737800
@Override
738801
public MetricSnapshots collect() {
739802
// Take a reference to the current config and collect with this one
@@ -754,6 +817,7 @@ public MetricSnapshots collect() {
754817
config.includeObjectNames,
755818
config.excludeObjectNames,
756819
config.objectNameAttributeFilter,
820+
config.metricCustomizers,
757821
receiver,
758822
jmxMBeanPropertyCache);
759823

collector/src/main/java/io/prometheus/jmx/JmxScraper.java

+53-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import java.util.Optional;
3232
import java.util.Set;
3333
import java.util.TreeSet;
34+
import java.util.Collections;
35+
import java.util.stream.Collectors;
3436
import javax.management.Attribute;
3537
import javax.management.AttributeList;
3638
import javax.management.JMException;
@@ -72,6 +74,7 @@ public interface MBeanReceiver {
7274
void recordBean(
7375
String domain,
7476
LinkedHashMap<String, String> beanProperties,
77+
Map<String, String> attributesAsLabelsWithValues,
7578
LinkedList<String> attrKeys,
7679
String attrName,
7780
String attrType,
@@ -85,6 +88,7 @@ void recordBean(
8588
private final String password;
8689
private final boolean ssl;
8790
private final List<ObjectName> includeObjectNames, excludeObjectNames;
91+
private final List<JmxCollector.MetricCustomizer> metricCustomizers;
8892
private final ObjectNameAttributeFilter objectNameAttributeFilter;
8993
private final JmxMBeanPropertyCache jmxMBeanPropertyCache;
9094

@@ -109,6 +113,7 @@ public JmxScraper(
109113
List<ObjectName> includeObjectNames,
110114
List<ObjectName> excludeObjectNames,
111115
ObjectNameAttributeFilter objectNameAttributeFilter,
116+
List<JmxCollector.MetricCustomizer> metricCustomizers,
112117
MBeanReceiver receiver,
113118
JmxMBeanPropertyCache jmxMBeanPropertyCache) {
114119
this.jmxUrl = jmxUrl;
@@ -118,6 +123,7 @@ public JmxScraper(
118123
this.ssl = ssl;
119124
this.includeObjectNames = includeObjectNames;
120125
this.excludeObjectNames = excludeObjectNames;
126+
this.metricCustomizers = metricCustomizers;
121127
this.objectNameAttributeFilter = objectNameAttributeFilter;
122128
this.jmxMBeanPropertyCache = jmxMBeanPropertyCache;
123129
}
@@ -253,6 +259,12 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
253259

254260
final String mBeanNameString = mBeanName.toString();
255261
final String mBeanDomain = mBeanName.getDomain();
262+
JmxCollector.MetricCustomizer metricCustomizer = getMetricCustomizer(mBeanName);
263+
Map<String, String> attributesAsLabelsWithValues = Collections.emptyMap();
264+
if (metricCustomizer != null) {
265+
attributesAsLabelsWithValues =
266+
getAttributesAsLabelsWithValues(metricCustomizer, attributes);
267+
}
256268

257269
for (Object object : attributes) {
258270
// The contents of an AttributeList should all be Attribute instances, but we'll verify
@@ -280,6 +292,7 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
280292
mBeanName,
281293
mBeanDomain,
282294
jmxMBeanPropertyCache.getKeyPropertyList(mBeanName),
295+
attributesAsLabelsWithValues,
283296
new LinkedList<>(),
284297
mBeanAttributeInfo.getName(),
285298
mBeanAttributeInfo.getType(),
@@ -300,6 +313,35 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
300313
}
301314
}
302315

316+
private Map<String, String> getAttributesAsLabelsWithValues(JmxCollector.MetricCustomizer metricCustomizer, AttributeList attributes) {
317+
Map<String, Object> attributeMap = attributes.asList().stream()
318+
.collect(Collectors.toMap(Attribute::getName, Attribute::getValue));
319+
Map<String, String> attributesAsLabelsWithValues = new HashMap<>();
320+
for (String attributeAsLabel : metricCustomizer.attributesAsLabels) {
321+
Object attrValue = attributeMap.get(attributeAsLabel);
322+
if (attrValue != null) {
323+
attributesAsLabelsWithValues.put(attributeAsLabel, attrValue.toString());
324+
}
325+
}
326+
return attributesAsLabelsWithValues;
327+
}
328+
329+
private JmxCollector.MetricCustomizer getMetricCustomizer(ObjectName mBeanName) {
330+
if (!metricCustomizers.isEmpty()) {
331+
for (JmxCollector.MetricCustomizer metricCustomizer : metricCustomizers) {
332+
if (filterMbeanByDomainAndProperties(mBeanName, metricCustomizer)) {
333+
return metricCustomizer;
334+
}
335+
}
336+
}
337+
return null;
338+
}
339+
340+
private boolean filterMbeanByDomainAndProperties(ObjectName mBeanName, JmxCollector.MetricCustomizer metricCustomizer) {
341+
return metricCustomizer.mbeanFilter.domain.equals(mBeanName.getDomain()) &&
342+
mBeanName.getKeyPropertyList().entrySet().containsAll(metricCustomizer.mbeanFilter.properties.entrySet());
343+
}
344+
303345
private void processAttributesOneByOne(
304346
MBeanServerConnection beanConn,
305347
ObjectName mbeanName,
@@ -318,6 +360,7 @@ private void processAttributesOneByOne(
318360
mbeanName,
319361
mbeanName.getDomain(),
320362
jmxMBeanPropertyCache.getKeyPropertyList(mbeanName),
363+
new HashMap<>(),
321364
new LinkedList<>(),
322365
attr.getName(),
323366
attr.getType(),
@@ -335,6 +378,7 @@ private void processBeanValue(
335378
ObjectName objectName,
336379
String domain,
337380
LinkedHashMap<String, String> beanProperties,
381+
Map<String, String> attributesAsLabelsWithValues,
338382
LinkedList<String> attrKeys,
339383
String attrName,
340384
String attrType,
@@ -352,7 +396,7 @@ private void processBeanValue(
352396
}
353397
LOGGER.log(FINE, "%s%s%s scrape: %s", domain, beanProperties, attrName, value);
354398
this.receiver.recordBean(
355-
domain, beanProperties, attrKeys, attrName, attrType, attrDescription, value);
399+
domain, beanProperties, attributesAsLabelsWithValues, attrKeys, attrName, attrType, attrDescription, value);
356400
} else if (value instanceof CompositeData) {
357401
LOGGER.log(FINE, "%s%s%s scrape: compositedata", domain, beanProperties, attrName);
358402
CompositeData composite = (CompositeData) value;
@@ -366,6 +410,7 @@ private void processBeanValue(
366410
objectName,
367411
domain,
368412
beanProperties,
413+
attributesAsLabelsWithValues,
369414
attrKeys,
370415
key,
371416
typ,
@@ -432,6 +477,7 @@ private void processBeanValue(
432477
objectName,
433478
domain,
434479
l2s,
480+
attributesAsLabelsWithValues,
435481
attrNames,
436482
name,
437483
typ,
@@ -452,6 +498,7 @@ private void processBeanValue(
452498
objectName,
453499
domain,
454500
beanProperties,
501+
attributesAsLabelsWithValues,
455502
attrKeys,
456503
attrName,
457504
attrType,
@@ -464,6 +511,7 @@ private void processBeanValue(
464511
objectName,
465512
domain,
466513
beanProperties,
514+
attributesAsLabelsWithValues,
467515
attrKeys,
468516
attrName,
469517
attrType,
@@ -479,6 +527,7 @@ private static class StdoutWriter implements MBeanReceiver {
479527
public void recordBean(
480528
String domain,
481529
LinkedHashMap<String, String> beanProperties,
530+
Map<String, String> attributesAsLabelsWithValues,
482531
LinkedList<String> attrKeys,
483532
String attrName,
484533
String attrType,
@@ -503,6 +552,7 @@ public static void main(String[] args) throws Exception {
503552
objectNames,
504553
new LinkedList<>(),
505554
objectNameAttributeFilter,
555+
new LinkedList<>(),
506556
new StdoutWriter(),
507557
new JmxMBeanPropertyCache())
508558
.doScrape();
@@ -515,6 +565,7 @@ public static void main(String[] args) throws Exception {
515565
objectNames,
516566
new LinkedList<>(),
517567
objectNameAttributeFilter,
568+
new LinkedList<>(),
518569
new StdoutWriter(),
519570
new JmxMBeanPropertyCache())
520571
.doScrape();
@@ -527,6 +578,7 @@ public static void main(String[] args) throws Exception {
527578
objectNames,
528579
new LinkedList<>(),
529580
objectNameAttributeFilter,
581+
new LinkedList<>(),
530582
new StdoutWriter(),
531583
new JmxMBeanPropertyCache())
532584
.doScrape();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2023-present The Prometheus jmx_exporter Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.prometheus.jmx;
17+
18+
import javax.management.MBeanServer;
19+
import javax.management.ObjectName;
20+
21+
/** Class to implement CustomValueMBean */
22+
public interface CustomValueMBean {
23+
24+
/**
25+
* Method to get the value
26+
*
27+
* @return value
28+
*/
29+
Integer getValue();
30+
31+
/**
32+
* Method to get the text
33+
*
34+
* @return text
35+
*/
36+
String getText();
37+
}
38+
39+
/** Class to implement CustomValue */
40+
class CustomValue implements CustomValueMBean {
41+
42+
@Override
43+
public Integer getValue() {
44+
return 345;
45+
}
46+
47+
@Override
48+
public String getText() {
49+
return "value";
50+
}
51+
52+
public static void registerBean(MBeanServer mbs) throws javax.management.JMException {
53+
ObjectName mbeanName =
54+
new ObjectName("io.prometheus.jmx:type=customValue");
55+
CustomValueMBean mbean = new CustomValue();
56+
mbs.registerMBean(mbean, mbeanName);
57+
}
58+
}

0 commit comments

Comments
 (0)