001/*
002 * Copyright (C) 2015-present The Prometheus jmx_exporter Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package io.prometheus.jmx;
018
019import io.prometheus.jmx.logger.Logger;
020import io.prometheus.jmx.logger.LoggerFactory;
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.logging.Level;
028import javax.management.MalformedObjectNameException;
029import javax.management.ObjectName;
030
031/** Class to implement filtering of an MBean's attributes based on the attribute's name */
032@SuppressWarnings("unchecked")
033public class ObjectNameAttributeFilter {
034
035    private static final Logger LOGGER = LoggerFactory.getLogger(ObjectNameAttributeFilter.class);
036
037    /** Configuration constant to define a mapping of ObjectNames to attribute names */
038    public static final String EXCLUDE_OBJECT_NAME_ATTRIBUTES = "excludeObjectNameAttributes";
039
040    /** Configuration constant to enable auto ObjectName attributes filtering */
041    public static final String AUTO_EXCLUDE_OBJECT_NAME_ATTRIBUTES =
042            "autoExcludeObjectNameAttributes";
043
044    private final Map<ObjectName, Set<String>> configExcludeObjectNameAttributesMap;
045    private final Map<ObjectName, Set<String>> dynamicExcludeObjectNameAttributesMap;
046
047    private boolean autoExcludeObjectNameAttributes;
048
049    /** Constructor */
050    private ObjectNameAttributeFilter() {
051        configExcludeObjectNameAttributesMap = new ConcurrentHashMap<>();
052        dynamicExcludeObjectNameAttributesMap = new ConcurrentHashMap<>();
053    }
054
055    /**
056     * Method to initialize the ObjectNameAttributeFilter
057     *
058     * @param yamlConfig yamlConfig
059     * @return an ObjectNameAttributeFilter
060     * @throws MalformedObjectNameException MalformedObjectNameException
061     */
062    private ObjectNameAttributeFilter initialize(Map<String, Object> yamlConfig)
063            throws MalformedObjectNameException {
064        if (yamlConfig.containsKey(EXCLUDE_OBJECT_NAME_ATTRIBUTES)) {
065            Map<Object, Object> objectNameAttributeMap =
066                    (Map<Object, Object>) yamlConfig.get(EXCLUDE_OBJECT_NAME_ATTRIBUTES);
067
068            for (Map.Entry<Object, Object> entry : objectNameAttributeMap.entrySet()) {
069                ObjectName objectName = new ObjectName((String) entry.getKey());
070
071                List<String> attributeNames = (List<String>) entry.getValue();
072
073                Set<String> attributeNameSet =
074                        configExcludeObjectNameAttributesMap.computeIfAbsent(
075                                objectName, o -> Collections.synchronizedSet(new HashSet<>()));
076
077                attributeNameSet.addAll(attributeNames);
078            }
079        }
080
081        if (yamlConfig.containsKey(AUTO_EXCLUDE_OBJECT_NAME_ATTRIBUTES)) {
082            autoExcludeObjectNameAttributes =
083                    (Boolean) yamlConfig.get(AUTO_EXCLUDE_OBJECT_NAME_ATTRIBUTES);
084        } else {
085            autoExcludeObjectNameAttributes = true;
086        }
087
088        LOGGER.log(Level.FINE, "dynamicExclusion [%b]", autoExcludeObjectNameAttributes);
089
090        return this;
091    }
092
093    /**
094     * Method to add an attribute name to the filter if dynamic exclusion is enabled
095     *
096     * @param objectName the ObjectName
097     * @param attributeName the attribute name
098     */
099    public void add(ObjectName objectName, String attributeName) {
100        if (autoExcludeObjectNameAttributes) {
101            Set<String> attribteNameSet =
102                    dynamicExcludeObjectNameAttributesMap.computeIfAbsent(
103                            objectName, o -> Collections.synchronizedSet(new HashSet<>()));
104
105            LOGGER.log(
106                    Level.FINE,
107                    "auto adding exclusion of object name [%s] attribute name [%s]",
108                    objectName.getCanonicalName(),
109                    attributeName);
110
111            attribteNameSet.add(attributeName);
112        }
113    }
114
115    /**
116     * Method to only keep "alive" mBeans, remove old mBeans to prevent memory growth
117     *
118     * @param aliveMBeans aliveMBeans
119     */
120    public void onlyKeepMBeans(Set<ObjectName> aliveMBeans) {
121        if (autoExcludeObjectNameAttributes) {
122            for (ObjectName prevName : dynamicExcludeObjectNameAttributesMap.keySet()) {
123                if (!aliveMBeans.contains(prevName)) {
124                    dynamicExcludeObjectNameAttributesMap.remove(prevName);
125                }
126            }
127        }
128    }
129
130    /**
131     * Method to check if an attribute should be excluded
132     *
133     * @param objectName the ObjectName
134     * @param attributeName the attribute name
135     * @return true if it should be excluded, false otherwise
136     */
137    public boolean exclude(ObjectName objectName, String attributeName) {
138        return exclude(configExcludeObjectNameAttributesMap, objectName, attributeName)
139                || exclude(dynamicExcludeObjectNameAttributesMap, objectName, attributeName);
140    }
141
142    private boolean exclude(
143            Map<ObjectName, Set<String>> exclusionMap,
144            ObjectName objectName,
145            String attributeName) {
146        boolean result = false;
147        if (!exclusionMap.isEmpty()) {
148            Set<String> attributeNameSet = exclusionMap.get(objectName);
149            if (attributeNameSet != null) {
150                result = attributeNameSet.contains(attributeName);
151            }
152        }
153        return result;
154    }
155
156    /**
157     * Method to create an ObjectNameAttributeFilter
158     *
159     * @param yamlConfig yamlConfig
160     * @return an ObjectNameAttributeFilter
161     */
162    public static ObjectNameAttributeFilter create(Map<String, Object> yamlConfig) {
163        try {
164            return new ObjectNameAttributeFilter().initialize(yamlConfig);
165        } catch (MalformedObjectNameException e) {
166            throw new RuntimeException(
167                    "Invalid configuration format for excludeObjectNameAttributes", e);
168        }
169    }
170}