001package io.prometheus.metrics.core.metrics;
002
003import io.prometheus.metrics.config.PrometheusProperties;
004import io.prometheus.metrics.model.snapshots.Labels;
005import io.prometheus.metrics.model.snapshots.MetricMetadata;
006import io.prometheus.metrics.model.snapshots.PrometheusNaming;
007import io.prometheus.metrics.model.snapshots.Unit;
008
009import java.util.Arrays;
010import java.util.List;
011
012/**
013 * Almost all metrics have fixed metadata, i.e. the metric name is known when the metric is created.
014 * <p>
015 * An exception would be a metric that is a bridge to a 3rd party metric library, where the metric name
016 * has to be retrieved from the 3rd party metric library at scrape time.
017 */
018public abstract class MetricWithFixedMetadata extends Metric {
019
020    private final MetricMetadata metadata;
021    protected final String[] labelNames;
022
023    protected MetricWithFixedMetadata(Builder<?, ?> builder) {
024        super(builder);
025        this.metadata = new MetricMetadata(makeName(builder.name, builder.unit), builder.help, builder.unit);
026        this.labelNames = Arrays.copyOf(builder.labelNames, builder.labelNames.length);
027    }
028
029    protected MetricMetadata getMetadata() {
030        return metadata;
031    }
032
033    private String makeName(String name, Unit unit) {
034        if (unit != null) {
035            if (!name.endsWith(unit.toString())) {
036                name = name + "_" + unit;
037            }
038        }
039        return name;
040    }
041
042    @Override
043    public String getPrometheusName() {
044        return metadata.getPrometheusName();
045    }
046
047    public static abstract class Builder<B extends Builder<B, M>, M extends MetricWithFixedMetadata> extends Metric.Builder<B, M> {
048
049        protected String name;
050        private Unit unit;
051        private String help;
052        private String[] labelNames = new String[0];
053
054        protected Builder(List<String> illegalLabelNames, PrometheusProperties properties) {
055            super(illegalLabelNames, properties);
056        }
057
058        public B name(String name) {
059            if (!PrometheusNaming.isValidMetricName(name)) {
060                throw new IllegalArgumentException("'" + name + "': Illegal metric name.");
061            }
062            this.name = name;
063            return self();
064        }
065
066        public B unit(Unit unit) {
067            this.unit = unit;
068            return self();
069        }
070
071        public B help(String help) {
072            this.help = help;
073            return self();
074        }
075
076        public B labelNames(String... labelNames) {
077            for (String labelName : labelNames) {
078                if (!PrometheusNaming.isValidLabelName(labelName)) {
079                    throw new IllegalArgumentException(labelName + ": illegal label name");
080                }
081                if (illegalLabelNames.contains(labelName)) {
082                    throw new IllegalArgumentException(labelName + ": illegal label name for this metric type");
083                }
084                if (constLabels.contains(labelName)) {
085                    throw new IllegalArgumentException(labelName + ": duplicate label name");
086                }
087            }
088            this.labelNames = labelNames;
089            return self();
090        }
091
092        public B constLabels(Labels constLabels) {
093            for (String labelName : labelNames) {
094                if (constLabels.contains(labelName)) { // Labels.contains() treats dots like underscores
095                    throw new IllegalArgumentException(labelName + ": duplicate label name");
096                }
097            }
098            return super.constLabels(constLabels);
099        }
100
101        @Override
102        public abstract M build();
103
104        @Override
105        protected abstract B self();
106    }
107}