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}