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