001package io.prometheus.metrics.model.snapshots;
002
003import static java.util.Objects.requireNonNull;
004
005import io.prometheus.metrics.annotations.StableApi;
006import javax.annotation.Nullable;
007
008/** Immutable representation of an Exemplar. */
009@StableApi
010public class Exemplar {
011
012  /** Label name for trace id. */
013  public static final String TRACE_ID = "trace_id";
014
015  /** Label name for span id. */
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 or use the
024   * 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
030   *     available.
031   */
032  public Exemplar(double value, Labels labels, long timestampMillis) {
033    if (labels == null) {
034      throw new NullPointerException("Labels cannot be null. Use Labels.EMPTY.");
035    }
036    this.value = value;
037    this.labels = labels;
038    this.timestampMillis = timestampMillis;
039  }
040
041  public double getValue() {
042    return value;
043  }
044
045  /**
046   * In most cases labels will contain {@link #TRACE_ID} and {@link #SPAN_ID}, but this is not
047   * required. May be {@link Labels#EMPTY}, but may not be {@code null}.
048   */
049  public Labels getLabels() {
050    return labels;
051  }
052
053  public boolean hasTimestamp() {
054    return timestampMillis != 0L;
055  }
056
057  /** Will return garbage if {@link #hasTimestamp()} is {@code false}. */
058  public long getTimestampMillis() {
059    return timestampMillis;
060  }
061
062  public static Builder builder() {
063    return new Builder();
064  }
065
066  public static class Builder {
067
068    @Nullable private Double value = null;
069    private Labels labels = Labels.EMPTY;
070    @Nullable private String traceId = null;
071    @Nullable private String spanId = null;
072    private long timestampMillis = 0L;
073
074    private Builder() {}
075
076    public Builder value(double value) {
077      this.value = value;
078      return this;
079    }
080
081    public Builder traceId(String traceId) {
082      this.traceId = traceId;
083      return this;
084    }
085
086    public Builder spanId(String spanId) {
087      this.spanId = spanId;
088      return this;
089    }
090
091    public Builder labels(Labels labels) {
092      this.labels = requireNonNull(labels, "Labels must not be null.");
093      return this;
094    }
095
096    public Builder timestampMillis(long timestampMillis) {
097      this.timestampMillis = timestampMillis;
098      return this;
099    }
100
101    /**
102     * @throws IllegalStateException if {@link #value(double)} wasn't called.
103     */
104    public Exemplar build() {
105      if (value == null) {
106        throw new IllegalStateException("cannot build an Exemplar without a value");
107      }
108      Labels allLabels;
109      if (traceId != null && spanId != null) {
110        allLabels = Labels.of(TRACE_ID, traceId, SPAN_ID, spanId);
111      } else if (traceId != null) {
112        allLabels = Labels.of(TRACE_ID, traceId);
113      } else if (spanId != null) {
114        allLabels = Labels.of(SPAN_ID, spanId);
115      } else {
116        allLabels = Labels.EMPTY;
117      }
118      if (!labels.isEmpty()) {
119        allLabels = allLabels.merge(labels);
120      }
121      return new Exemplar(value, allLabels, timestampMillis);
122    }
123  }
124}