001/*
002 * Copyright (C) 2020-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 java.util.Collection;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024import java.util.concurrent.ConcurrentHashMap;
025
026/**
027 * MatchedRulesCache is a cache for bean name to configured rule mapping (See
028 * JmxCollector.Receiver). The cache also retains unmatched entries (a bean name not matching a rule
029 * pattern) to avoid matching against the same pattern in later bean collections.
030 */
031public class MatchedRulesCache {
032    private final Map<JmxCollector.Rule, Map<String, MatchedRule>> cachedRules;
033
034    public MatchedRulesCache(Collection<JmxCollector.Rule> rules) {
035        this.cachedRules = new HashMap<>(rules.size());
036        for (JmxCollector.Rule rule : rules) {
037            this.cachedRules.put(rule, new ConcurrentHashMap<>());
038        }
039    }
040
041    public void put(
042            final JmxCollector.Rule rule, final String cacheKey, final MatchedRule matchedRule) {
043        Map<String, MatchedRule> cachedRulesForRule = cachedRules.get(rule);
044        cachedRulesForRule.put(cacheKey, matchedRule);
045    }
046
047    public MatchedRule get(final JmxCollector.Rule rule, final String cacheKey) {
048        return cachedRules.get(rule).get(cacheKey);
049    }
050
051    // Remove stale rules (in the cache but not collected in the last run of the collector)
052    public void evictStaleEntries(final StalenessTracker stalenessTracker) {
053        for (Map.Entry<JmxCollector.Rule, Map<String, MatchedRule>> entry :
054                cachedRules.entrySet()) {
055            JmxCollector.Rule rule = entry.getKey();
056            Map<String, MatchedRule> cachedRulesForRule = entry.getValue();
057
058            for (String cacheKey : cachedRulesForRule.keySet()) {
059                if (!stalenessTracker.contains(rule, cacheKey)) {
060                    cachedRulesForRule.remove(cacheKey);
061                }
062            }
063        }
064    }
065
066    public static class StalenessTracker {
067        private final Map<JmxCollector.Rule, Set<String>> lastCachedEntries = new HashMap<>();
068
069        public void add(final JmxCollector.Rule rule, final String cacheKey) {
070            Set<String> lastCachedEntriesForRule =
071                    lastCachedEntries.computeIfAbsent(rule, k -> new HashSet<>());
072            lastCachedEntriesForRule.add(cacheKey);
073        }
074
075        public boolean contains(final JmxCollector.Rule rule, final String cacheKey) {
076            Set<String> lastCachedEntriesForRule = lastCachedEntries.get(rule);
077            return (lastCachedEntriesForRule != null)
078                    && lastCachedEntriesForRule.contains(cacheKey);
079        }
080
081        public long cachedCount() {
082            long count = 0;
083            for (Set<String> cacheKeys : lastCachedEntries.values()) {
084                count += cacheKeys.size();
085            }
086            return count;
087        }
088    }
089}