001/*
002 * Copyright (C) 2024-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 io.prometheus.metrics.model.snapshots.*;
022import java.util.*;
023import java.util.logging.Level;
024
025public class MatchedRuleToMetricSnapshotsConverter {
026
027    private static final Logger LOGGER =
028            LoggerFactory.getLogger(MatchedRuleToMetricSnapshotsConverter.class);
029
030    private static final String OBJECTNAME = "_objectname";
031
032    public static MetricSnapshots convert(List<MatchedRule> matchedRules) {
033        Map<String, List<MatchedRule>> rulesByPrometheusMetricName = new HashMap<>();
034
035        for (MatchedRule matchedRule : matchedRules) {
036            List<MatchedRule> matchedRulesWithSameName =
037                    rulesByPrometheusMetricName.computeIfAbsent(
038                            matchedRule.name, name -> new ArrayList<>());
039            matchedRulesWithSameName.add(matchedRule);
040        }
041
042        if (LOGGER.isLoggable(Level.FINE)) {
043            rulesByPrometheusMetricName
044                    .values()
045                    .forEach(
046                            matchedRules1 ->
047                                    matchedRules1.forEach(
048                                            matchedRule ->
049                                                    LOGGER.log(
050                                                            Level.FINE,
051                                                            "matchedRule %s",
052                                                            matchedRule)));
053        }
054
055        MetricSnapshots.Builder result = MetricSnapshots.builder();
056        for (List<MatchedRule> rulesWithSameName : rulesByPrometheusMetricName.values()) {
057            result.metricSnapshot(convertRulesWithSameName(rulesWithSameName));
058        }
059        return result.build();
060    }
061
062    private static MetricSnapshot convertRulesWithSameName(List<MatchedRule> rulesWithSameName) {
063        boolean labelsUnique = isLabelsUnique(rulesWithSameName);
064        switch (getType(rulesWithSameName)) {
065            case "COUNTER":
066                CounterSnapshot.Builder counterBuilder =
067                        CounterSnapshot.builder()
068                                .name(rulesWithSameName.get(0).name)
069                                .help(rulesWithSameName.get(0).help);
070                for (MatchedRule rule : rulesWithSameName) {
071                    Labels labels = Labels.of(rule.labelNames, rule.labelValues);
072                    if (!labelsUnique) {
073                        labels =
074                                labels.merge(
075                                        Labels.of(
076                                                OBJECTNAME,
077                                                rule.matchName.substring(
078                                                        0, rule.matchName.lastIndexOf(":"))));
079                    }
080                    counterBuilder.dataPoint(
081                            CounterSnapshot.CounterDataPointSnapshot.builder()
082                                    .labels(labels)
083                                    .value(rule.value)
084                                    .build());
085                }
086                return counterBuilder.build();
087            case "GAUGE":
088                GaugeSnapshot.Builder gaugeBuilder =
089                        GaugeSnapshot.builder()
090                                .name(rulesWithSameName.get(0).name)
091                                .help(rulesWithSameName.get(0).help);
092                for (MatchedRule rule : rulesWithSameName) {
093                    Labels labels = Labels.of(rule.labelNames, rule.labelValues);
094                    if (!labelsUnique) {
095                        labels =
096                                labels.merge(
097                                        Labels.of(
098                                                OBJECTNAME,
099                                                rule.matchName.substring(
100                                                        0, rule.matchName.lastIndexOf(":"))));
101                    }
102                    gaugeBuilder.dataPoint(
103                            GaugeSnapshot.GaugeDataPointSnapshot.builder()
104                                    .labels(labels)
105                                    .value(rule.value)
106                                    .build());
107                }
108                return gaugeBuilder.build();
109            default:
110                UnknownSnapshot.Builder unknownBuilder =
111                        UnknownSnapshot.builder()
112                                .name(rulesWithSameName.get(0).name)
113                                .help(rulesWithSameName.get(0).help);
114                for (MatchedRule rule : rulesWithSameName) {
115                    Labels labels = Labels.of(rule.labelNames, rule.labelValues);
116                    if (!labelsUnique) {
117                        labels =
118                                labels.merge(
119                                        Labels.of(
120                                                OBJECTNAME,
121                                                rule.matchName.substring(
122                                                        0, rule.matchName.lastIndexOf(":"))));
123                    }
124                    unknownBuilder.dataPoint(
125                            UnknownSnapshot.UnknownDataPointSnapshot.builder()
126                                    .labels(labels)
127                                    .value(rule.value)
128                                    .build());
129                }
130                return unknownBuilder.build();
131        }
132    }
133
134    /** If all rules have the same type, that type is returned. Otherwise, "UNKNOWN" is returned. */
135    private static String getType(List<MatchedRule> rulesWithSameName) {
136        if (rulesWithSameName.stream().map(rule -> rule.type).distinct().count() == 1) {
137            return rulesWithSameName.get(0).type;
138        }
139        return "UNKNOWN";
140    }
141
142    private static boolean isLabelsUnique(List<MatchedRule> rulesWithSameName) {
143        Set<Labels> labelsSet = new HashSet<>(rulesWithSameName.size());
144        for (MatchedRule matchedRule : rulesWithSameName) {
145            Labels labels = Labels.of(matchedRule.labelNames, matchedRule.labelValues);
146            if (labelsSet.contains(labels)) {
147                return false;
148            }
149            labelsSet.add(labels);
150        }
151        return true;
152    }
153}