001package io.prometheus.metrics.model.snapshots;
002
003/**
004 * Immutable representation of an Exemplar.
005 */
006public class Exemplar {
007
008    /**
009     * Label name for trace id.
010     */
011    public static final String TRACE_ID = "trace_id";
012
013    /**
014     * Label name for span id.
015     */
016    public static final String SPAN_ID = "span_id";
017
018    private final double value;
019    private final Labels labels;
020    private final long timestampMillis;
021
022    /**
023     * To create a new {@link Exemplar}, you can either call the constructor directly
024     * or use the Builder with {@link Exemplar#builder()}.
025     *
026     * @param value           the observed value. This is required.
027     * @param labels          in most cases the labels will contain the {@link #TRACE_ID} and {@link #SPAN_ID}.
028     *                        Must not be {@code null}. Use {@link Labels#EMPTY} if no labels are present.
029     * @param timestampMillis timestamp when the value was observed. Optional. Use 0L if not available.
030     */
031    public Exemplar(double value, Labels labels, long timestampMillis) {
032        if (labels == null) {
033            throw new NullPointerException("Labels cannot be null. Use Labels.EMPTY.");
034        }
035        this.value = value;
036        this.labels = labels;
037        this.timestampMillis = timestampMillis;
038    }
039
040    public double getValue() {
041        return value;
042    }
043
044    /**
045     * In most cases labels will contain {@link #TRACE_ID} and {@link #SPAN_ID}, but this is not required.
046     * May be {@link Labels#EMPTY}, but may not be {@code null}.
047     */
048    public Labels getLabels() {
049        return labels;
050    }
051
052    public boolean hasTimestamp() {
053        return timestampMillis != 0L;
054    }
055
056    /**
057     * Will return garbage if {@link #hasTimestamp()} is {@code false}.
058     */
059    public long getTimestampMillis() {
060        return timestampMillis;
061    }
062
063    public static Builder builder() {
064        return new Builder();
065    }
066
067    public static class Builder {
068
069        private Double value = null;
070        private Labels labels = Labels.EMPTY;
071        private String traceId = null;
072        private String spanId = null;
073        private long timestampMillis = 0L;
074
075        private Builder() {
076        }
077
078        public Builder value(double value) {
079            this.value = value;
080            return this;
081        }
082
083        public Builder traceId(String traceId) {
084            this.traceId = traceId;
085            return this;
086        }
087
088        public Builder spanId(String spanId) {
089            this.spanId = spanId;
090            return this;
091        }
092
093        public Builder labels(Labels labels) {
094            if (labels == null) {
095                throw new NullPointerException();
096            }
097            this.labels = labels;
098            return this;
099        }
100
101        public Builder timestampMillis(long timestampMillis) {
102            this.timestampMillis = timestampMillis;
103            return this;
104        }
105
106        /**
107         * @throws IllegalStateException if {@link #value(double)} wasn't called.
108         */
109        public Exemplar build() {
110            if (value == null) {
111                throw new IllegalStateException("cannot build an Exemplar without a value");
112            }
113            Labels allLabels;
114            if (traceId != null && spanId != null) {
115                allLabels = Labels.of(TRACE_ID, traceId, SPAN_ID, spanId);
116            } else if (traceId != null) {
117                allLabels = Labels.of(TRACE_ID, traceId);
118            } else if (spanId != null) {
119                allLabels = Labels.of(SPAN_ID, spanId);
120            } else {
121                allLabels = Labels.EMPTY;
122            }
123            if (!labels.isEmpty()) {
124                allLabels = allLabels.merge(labels);
125            }
126            return new Exemplar(value, allLabels, timestampMillis);
127        }
128    }
129}