001package io.prometheus.metrics.model.snapshots;
002
003import java.util.ArrayList;
004import java.util.Arrays;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.List;
009
010/**
011 * Base class for metric snapshots.
012 */
013public abstract class MetricSnapshot {
014
015    private final MetricMetadata metadata;
016    protected final List<? extends DataPointSnapshot> dataPoints;
017
018    protected MetricSnapshot(MetricMetadata metadata, DataPointSnapshot... dataPoints) {
019        this(metadata, Arrays.asList(dataPoints));
020    }
021
022    protected MetricSnapshot(MetricMetadata metadata, Collection<? extends DataPointSnapshot> dataPoints) {
023        if (metadata == null) {
024            throw new NullPointerException("metadata");
025        }
026        if (dataPoints == null) {
027            throw new NullPointerException("dataPoints");
028        }
029        this.metadata = metadata;
030        List<? extends DataPointSnapshot> dataCopy = new ArrayList<>(dataPoints);
031        dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels));
032        this.dataPoints = Collections.unmodifiableList(dataCopy);
033        validateLabels();
034    }
035
036    public MetricMetadata getMetadata() {
037        return metadata;
038    }
039
040    public abstract List<? extends DataPointSnapshot> getDataPoints();
041
042    protected void validateLabels() {
043        // Verify that labels are unique (the same set of names/values must not be used multiple times for the same metric).
044        for (int i = 0; i < dataPoints.size() - 1; i++) {
045            if (dataPoints.get(i).getLabels().equals(dataPoints.get(i + 1).getLabels())) {
046                throw new DuplicateLabelsException(metadata, dataPoints.get(i).getLabels());
047            }
048        }
049        // Should we verify that all entries in data have the same label names?
050        // No. They should have the same label names, but according to OpenMetrics this is not a MUST.
051    }
052
053    public static abstract class Builder<T extends Builder<T>> {
054
055        private String name;
056        private String help;
057        private Unit unit;
058
059        /**
060         * The name is required.
061         * If the name is missing or invalid, {@code build()} will throw an {@link IllegalArgumentException}.
062         * See {@link PrometheusNaming#isValidMetricName(String)} for info on valid metric names.
063         */
064        public T name(String name) {
065            this.name = name;
066            return self();
067        }
068
069        public T help(String help) {
070            this.help = help;
071            return self();
072        }
073
074        public T unit(Unit unit) {
075            this.unit = unit;
076            return self();
077        }
078
079        public abstract MetricSnapshot build();
080
081        protected MetricMetadata buildMetadata() {
082            return new MetricMetadata(name, help, unit);
083        }
084
085        protected abstract T self();
086    }
087}