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