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}