Skip to content

Commit 3689d53

Browse files
authored
Added MBean attribute exclusion filtering (#870)
* Added MBean attribute exclusion filtering Signed-off-by: Doug Hoard <doug.hoard@gmail.com>
1 parent 044d5aa commit 3689d53

File tree

16 files changed

+531
-1
lines changed

16 files changed

+531
-1
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ lowercaseOutputName: false
8383
lowercaseOutputLabelNames: false
8484
includeObjectNames: ["org.apache.cassandra.metrics:*"]
8585
excludeObjectNames: ["org.apache.cassandra.metrics:type=ColumnFamily,*"]
86+
excludeObjectNameAttributes:
87+
"java.lang:type=OperatingSystem":
88+
- "ObjectName"
89+
"java.lang:type=Runtime":
90+
- "ClassPath"
91+
- "SystemProperties"
8692
rules:
8793
- pattern: 'org.apache.cassandra.metrics<type=(\w+), name=(\w+)><>Value: (\d+)'
8894
name: cassandra_$1_$2
@@ -106,6 +112,8 @@ lowercaseOutputName | Lowercase the output metric name. Applies to default forma
106112
lowercaseOutputLabelNames | Lowercase the output metric label names. Applies to default format and `labels`. Defaults to false.
107113
includeObjectNames | A list of [ObjectNames](http://docs.oracle.com/javase/6/docs/api/javax/management/ObjectName.html) to query. Defaults to all mBeans.
108114
excludeObjectNames | A list of [ObjectNames](http://docs.oracle.com/javase/6/docs/api/javax/management/ObjectName.html) to not query. Takes precedence over `includeObjectNames`. Defaults to none.
115+
excludeObjectNameAttributesDynamic | Whether to use dynamic [ObjectName](http://docs.oracle.com/javase/6/docs/api/javax/management/ObjectName.html) attribute filtering. Defaults to false.
116+
excludeObjectNameAttributes | Optional a map of [ObjectNames](http://docs.oracle.com/javase/6/docs/api/javax/management/ObjectName.html) with a list of attribute names.
109117
rules | A list of rules to apply in order, processing stops at the first matching rule. Attributes that aren't matched aren't collected. If not specified, defaults to collecting everything in the default format.
110118
pattern | Regex pattern to match against each bean attribute. The pattern is not anchored. Capture groups can be used in other options. Defaults to matching everything.
111119
attrNameSnakeCase | Converts the attribute name to snake case. This is seen in the names matched by the pattern and the default format. For example, anAttrName to an\_attr\_name. Defaults to false.

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ private static class Config {
9393
boolean lowercaseOutputLabelNames;
9494
List<ObjectName> includeObjectNames = new ArrayList<>();
9595
List<ObjectName> excludeObjectNames = new ArrayList<>();
96+
ObjectNameAttributeFilter objectNameAttributeFilter;
9697
List<Rule> rules = new ArrayList<>();
9798
long lastUpdate = 0L;
9899

@@ -323,6 +324,7 @@ private Config loadConfig(Map<String, Object> yamlConfig) throws MalformedObject
323324
}
324325

325326
cfg.rulesCache = new MatchedRulesCache(cfg.rules);
327+
cfg.objectNameAttributeFilter = ObjectNameAttributeFilter.create(yamlConfig);
326328

327329
return cfg;
328330
}
@@ -756,6 +758,7 @@ public List<MetricFamilySamples> collect() {
756758
config.ssl,
757759
config.includeObjectNames,
758760
config.excludeObjectNames,
761+
config.objectNameAttributeFilter,
759762
receiver,
760763
jmxMBeanPropertyCache);
761764
long start = System.nanoTime();

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

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ void recordBean(
7171
private final String password;
7272
private final boolean ssl;
7373
private final List<ObjectName> includeObjectNames, excludeObjectNames;
74+
private final ObjectNameAttributeFilter objectNameAttributeFilter;
7475
private final JmxMBeanPropertyCache jmxMBeanPropertyCache;
7576

7677
public JmxScraper(
@@ -80,6 +81,7 @@ public JmxScraper(
8081
boolean ssl,
8182
List<ObjectName> includeObjectNames,
8283
List<ObjectName> excludeObjectNames,
84+
ObjectNameAttributeFilter objectNameAttributeFilter,
8385
MBeanReceiver receiver,
8486
JmxMBeanPropertyCache jmxMBeanPropertyCache) {
8587
this.jmxUrl = jmxUrl;
@@ -89,6 +91,7 @@ public JmxScraper(
8991
this.ssl = ssl;
9092
this.includeObjectNames = includeObjectNames;
9193
this.excludeObjectNames = excludeObjectNames;
94+
this.objectNameAttributeFilter = objectNameAttributeFilter;
9295
this.jmxMBeanPropertyCache = jmxMBeanPropertyCache;
9396
}
9497

@@ -174,9 +177,18 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
174177
LOGGER.log(FINE, "%s_%s not readable", mBeanName, mBeanAttributeInfo.getName());
175178
continue;
176179
}
180+
181+
if (objectNameAttributeFilter.exclude(mBeanName, mBeanAttributeInfo.getName())) {
182+
continue;
183+
}
184+
177185
name2MBeanAttributeInfo.put(mBeanAttributeInfo.getName(), mBeanAttributeInfo);
178186
}
179187

188+
if (name2MBeanAttributeInfo.isEmpty()) {
189+
return;
190+
}
191+
180192
AttributeList attributes;
181193

182194
try {
@@ -221,6 +233,7 @@ private void scrapeBean(MBeanServerConnection beanConn, ObjectName mBeanName) {
221233
name2MBeanAttributeInfo.get(attribute.getName());
222234
LOGGER.log(FINE, "%s_%s process", mBeanName, mBeanAttributeInfo.getName());
223235
processBeanValue(
236+
mBeanName,
224237
mBeanName.getDomain(),
225238
jmxMBeanPropertyCache.getKeyPropertyList(mBeanName),
226239
new LinkedList<>(),
@@ -253,6 +266,7 @@ private void processAttributesOneByOne(
253266

254267
LOGGER.log(FINE, "%s_%s process", mbeanName, attr.getName());
255268
processBeanValue(
269+
mbeanName,
256270
mbeanName.getDomain(),
257271
jmxMBeanPropertyCache.getKeyPropertyList(mbeanName),
258272
new LinkedList<>(),
@@ -269,6 +283,7 @@ private void processAttributesOneByOne(
269283
* pass of getting the values/names out in a way it can be processed elsewhere easily.
270284
*/
271285
private void processBeanValue(
286+
ObjectName objectName,
272287
String domain,
273288
LinkedHashMap<String, String> beanProperties,
274289
LinkedList<String> attrKeys,
@@ -299,7 +314,14 @@ private void processBeanValue(
299314
String typ = type.getType(key).getTypeName();
300315
Object valu = composite.get(key);
301316
processBeanValue(
302-
domain, beanProperties, attrKeys, key, typ, type.getDescription(), valu);
317+
objectName,
318+
domain,
319+
beanProperties,
320+
attrKeys,
321+
key,
322+
typ,
323+
type.getDescription(),
324+
valu);
303325
}
304326
} else if (value instanceof TabularData) {
305327
// I don't pretend to have a good understanding of TabularData.
@@ -358,6 +380,7 @@ private void processBeanValue(
358380
name = attrName;
359381
}
360382
processBeanValue(
383+
objectName,
361384
domain,
362385
l2s,
363386
attrNames,
@@ -377,6 +400,7 @@ private void processBeanValue(
377400
Optional<?> optional = (Optional<?>) value;
378401
if (optional.isPresent()) {
379402
processBeanValue(
403+
objectName,
380404
domain,
381405
beanProperties,
382406
attrKeys,
@@ -388,6 +412,7 @@ private void processBeanValue(
388412
} else if (value.getClass().isEnum()) {
389413
LOGGER.log(FINE, "%s%s%s scrape: %s", domain, beanProperties, attrName, value);
390414
processBeanValue(
415+
objectName,
391416
domain,
392417
beanProperties,
393418
attrKeys,
@@ -396,6 +421,7 @@ private void processBeanValue(
396421
attrDescription,
397422
value.toString());
398423
} else {
424+
objectNameAttributeFilter.add(objectName, attrName);
399425
LOGGER.log(FINE, "%s%s scrape: %s not exported", domain, beanProperties, attrType);
400426
}
401427
}
@@ -415,6 +441,8 @@ public void recordBean(
415441

416442
/** Convenience function to run standalone. */
417443
public static void main(String[] args) throws Exception {
444+
ObjectNameAttributeFilter objectNameAttributeFilter =
445+
ObjectNameAttributeFilter.create(new HashMap<>());
418446
List<ObjectName> objectNames = new LinkedList<>();
419447
objectNames.add(null);
420448
if (args.length >= 3) {
@@ -425,6 +453,7 @@ public static void main(String[] args) throws Exception {
425453
(args.length > 3 && "ssl".equalsIgnoreCase(args[3])),
426454
objectNames,
427455
new LinkedList<>(),
456+
objectNameAttributeFilter,
428457
new StdoutWriter(),
429458
new JmxMBeanPropertyCache())
430459
.doScrape();
@@ -436,6 +465,7 @@ public static void main(String[] args) throws Exception {
436465
false,
437466
objectNames,
438467
new LinkedList<>(),
468+
objectNameAttributeFilter,
439469
new StdoutWriter(),
440470
new JmxMBeanPropertyCache())
441471
.doScrape();
@@ -447,6 +477,7 @@ public static void main(String[] args) throws Exception {
447477
false,
448478
objectNames,
449479
new LinkedList<>(),
480+
objectNameAttributeFilter,
450481
new StdoutWriter(),
451482
new JmxMBeanPropertyCache())
452483
.doScrape();
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright (C) 2015-2023 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+
17+
package io.prometheus.jmx;
18+
19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.Set;
24+
import java.util.concurrent.ConcurrentHashMap;
25+
import javax.management.MalformedObjectNameException;
26+
import javax.management.ObjectName;
27+
28+
/** Class to implement filtering of an MBean's attributes based on the attribute's name */
29+
@SuppressWarnings("unchecked")
30+
public class ObjectNameAttributeFilter {
31+
32+
/** Configuration constant to define a mapping of ObjectNames to attribute names */
33+
public static final String EXCLUDE_OBJECT_NAME_ATTRIBUTES = "excludeObjectNameAttributes";
34+
35+
/** Configuration constant to enable dynamic support of ObjectName attributes filtering */
36+
public static final String EXCLUDE_OBJECT_NAME_ATTRIBUTES_DYNAMIC =
37+
EXCLUDE_OBJECT_NAME_ATTRIBUTES + "Dynamic";
38+
39+
private final Map<ObjectName, Set<String>> excludeObjectNameAttributesMap;
40+
41+
private boolean dynamicExclusion;
42+
43+
/** Constructor */
44+
private ObjectNameAttributeFilter() {
45+
excludeObjectNameAttributesMap = new ConcurrentHashMap<>();
46+
}
47+
48+
/**
49+
* Method to initialize the ObjectNameAttributeFilter
50+
*
51+
* @param yamlConfig yamlConfig
52+
* @return an ObjectNameAttributeFilter
53+
* @throws MalformedObjectNameException MalformedObjectNameException
54+
*/
55+
private ObjectNameAttributeFilter initialize(Map<String, Object> yamlConfig)
56+
throws MalformedObjectNameException {
57+
if (yamlConfig.containsKey(EXCLUDE_OBJECT_NAME_ATTRIBUTES)) {
58+
Map<Object, Object> objectNameAttributeMap =
59+
(Map<Object, Object>) yamlConfig.get(EXCLUDE_OBJECT_NAME_ATTRIBUTES);
60+
61+
for (Map.Entry<Object, Object> entry : objectNameAttributeMap.entrySet()) {
62+
ObjectName objectName = new ObjectName((String) entry.getKey());
63+
64+
List<String> attributeNames = (List<String>) entry.getValue();
65+
66+
Set<String> attributeNameSet =
67+
excludeObjectNameAttributesMap.computeIfAbsent(
68+
objectName, o -> Collections.synchronizedSet(new HashSet<>()));
69+
70+
for (String attributeName : attributeNames) {
71+
attributeNameSet.add(attributeName);
72+
}
73+
74+
excludeObjectNameAttributesMap.put(objectName, attributeNameSet);
75+
}
76+
}
77+
78+
if (yamlConfig.containsKey(EXCLUDE_OBJECT_NAME_ATTRIBUTES_DYNAMIC)) {
79+
dynamicExclusion = (Boolean) yamlConfig.get(EXCLUDE_OBJECT_NAME_ATTRIBUTES_DYNAMIC);
80+
}
81+
82+
return this;
83+
}
84+
85+
/**
86+
* Method to add an attribute name to the filter if dynamic exclusion is enabled
87+
*
88+
* @param objectName the ObjectName
89+
* @param attributeName the attribute name
90+
*/
91+
public void add(ObjectName objectName, String attributeName) {
92+
if (dynamicExclusion) {
93+
Set<String> attribteNameSet =
94+
excludeObjectNameAttributesMap.computeIfAbsent(
95+
objectName, o -> Collections.synchronizedSet(new HashSet<>()));
96+
97+
attribteNameSet.add(attributeName);
98+
}
99+
}
100+
101+
/**
102+
* Method to check if an attribute should be excluded
103+
*
104+
* @param objectName the ObjectName
105+
* @param attributeName the attribute name
106+
* @return true if it should be excluded, false otherwise
107+
*/
108+
public boolean exclude(ObjectName objectName, String attributeName) {
109+
boolean result = false;
110+
111+
if (excludeObjectNameAttributesMap.size() > 0) {
112+
Set<String> attributeNameSet = excludeObjectNameAttributesMap.get(objectName);
113+
if (attributeNameSet != null) {
114+
result = attributeNameSet.contains(attributeName);
115+
}
116+
}
117+
118+
return result;
119+
}
120+
121+
/**
122+
* Method to create an ObjectNameAttributeFilter
123+
*
124+
* @param yamlConfig yamlConfig
125+
* @return an ObjectNameAttributeFilter
126+
*/
127+
public static ObjectNameAttributeFilter create(Map<String, Object> yamlConfig) {
128+
try {
129+
return new ObjectNameAttributeFilter().initialize(yamlConfig);
130+
} catch (MalformedObjectNameException e) {
131+
throw new RuntimeException(
132+
"Invalid configuration format for excludeObjectNameAttributes", e);
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)