001package io.prometheus.metrics.instrumentation.dropwizard5.labels;
002
003import io.prometheus.metrics.annotations.StableApi;
004import io.prometheus.metrics.model.snapshots.Labels;
005import java.util.ArrayList;
006import java.util.List;
007import java.util.Map;
008
009/**
010 * A LabelMapper to allow Dropwizard metrics to be translated to Prometheus metrics including custom
011 * labels and names. Prometheus metric name and labels are extracted from the Dropwizard name based
012 * on the provided list of {@link MapperConfig}s. The FIRST matching config will be used.
013 */
014@StableApi
015public class CustomLabelMapper {
016  private final List<CompiledMapperConfig> compiledMapperConfigs;
017
018  public CustomLabelMapper(List<MapperConfig> mapperConfigs) {
019    if (mapperConfigs == null || mapperConfigs.isEmpty()) {
020      throw new IllegalArgumentException("CustomLabelMapper needs some mapper configs!");
021    }
022
023    this.compiledMapperConfigs = new ArrayList<>(mapperConfigs.size());
024    for (MapperConfig config : mapperConfigs) {
025      this.compiledMapperConfigs.add(new CompiledMapperConfig(config));
026    }
027  }
028
029  public String getName(String dropwizardName) {
030    if (dropwizardName == null) {
031      throw new IllegalArgumentException("Dropwizard metric name cannot be null");
032    }
033
034    CompiledMapperConfig matchingConfig = null;
035    for (CompiledMapperConfig config : this.compiledMapperConfigs) {
036      if (config.pattern.matches(dropwizardName)) {
037        matchingConfig = config;
038        break;
039      }
040    }
041
042    if (matchingConfig != null) {
043      final Map<String, String> params = matchingConfig.pattern.extractParameters(dropwizardName);
044      final NameAndLabels nameAndLabels = getNameAndLabels(matchingConfig.mapperConfig, params);
045      return nameAndLabels.name;
046    }
047
048    return dropwizardName;
049  }
050
051  public Labels getLabels(
052      final String dropwizardName,
053      final List<String> additionalLabelNames,
054      final List<String> additionalLabelValues) {
055    if (dropwizardName == null) {
056      throw new IllegalArgumentException("Dropwizard metric name cannot be null");
057    }
058
059    CompiledMapperConfig matchingConfig = null;
060    for (CompiledMapperConfig config : this.compiledMapperConfigs) {
061      if (config.pattern.matches(dropwizardName)) {
062        matchingConfig = config;
063        break;
064      }
065    }
066
067    if (matchingConfig != null) {
068      final Map<String, String> params = matchingConfig.pattern.extractParameters(dropwizardName);
069      final NameAndLabels nameAndLabels = getNameAndLabels(matchingConfig.mapperConfig, params);
070      nameAndLabels.labelNames.addAll(additionalLabelNames);
071      nameAndLabels.labelValues.addAll(additionalLabelValues);
072      return Labels.of(nameAndLabels.labelNames, nameAndLabels.labelValues);
073    }
074
075    return Labels.of(additionalLabelNames, additionalLabelValues);
076  }
077
078  @SuppressWarnings("NullAway") // not sure if it can be null here
079  protected NameAndLabels getNameAndLabels(MapperConfig config, Map<String, String> parameters) {
080    final String metricName = formatTemplate(config.getName(), parameters);
081    final List<String> labels = new ArrayList<String>(config.getLabels().size());
082    final List<String> labelValues = new ArrayList<String>(config.getLabels().size());
083    for (Map.Entry<String, String> entry : config.getLabels().entrySet()) {
084      labels.add(entry.getKey());
085      labelValues.add(formatTemplate(entry.getValue(), parameters));
086    }
087
088    return new NameAndLabels(metricName, labels, labelValues);
089  }
090
091  private String formatTemplate(String template, Map<String, String> params) {
092    String result = template;
093    for (Map.Entry<String, String> entry : params.entrySet()) {
094      result = result.replace(entry.getKey(), entry.getValue());
095    }
096
097    return result;
098  }
099
100  static class CompiledMapperConfig {
101    final MapperConfig mapperConfig;
102    final GraphiteNamePattern pattern;
103
104    @SuppressWarnings("NullAway") // not sure if it can be null here
105    CompiledMapperConfig(MapperConfig mapperConfig) {
106      this.mapperConfig = mapperConfig;
107      this.pattern = new GraphiteNamePattern(mapperConfig.getMatch());
108    }
109  }
110
111  static class NameAndLabels {
112    final String name;
113    final List<String> labelNames;
114    final List<String> labelValues;
115
116    NameAndLabels(
117        final String name, final List<String> labelNames, final List<String> labelValues) {
118      this.name = name;
119      this.labelNames = labelNames;
120      this.labelValues = labelValues;
121    }
122  }
123}