001package io.prometheus.metrics.model.snapshots; 002 003import io.prometheus.metrics.annotations.StableApi; 004import io.prometheus.metrics.config.EscapingScheme; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.List; 008import javax.annotation.Nullable; 009 010/** Immutable snapshot of a Counter. */ 011@StableApi 012public class CounterSnapshot extends MetricSnapshot { 013 014 /** 015 * To create a new {@link CounterSnapshot}, you can either call the constructor directly or use 016 * the builder with {@link CounterSnapshot#builder()}. 017 * 018 * @param metadata the metric name in metadata must not include the {@code _total} suffix. See 019 * {@link MetricMetadata} for more naming conventions. 020 * @param dataPoints the constructor will create a sorted copy of the collection. 021 */ 022 public CounterSnapshot(MetricMetadata metadata, Collection<CounterDataPointSnapshot> dataPoints) { 023 this(metadata, dataPoints, false); 024 } 025 026 private CounterSnapshot( 027 MetricMetadata metadata, Collection<CounterDataPointSnapshot> dataPoints, boolean internal) { 028 super(metadata, dataPoints, internal); 029 } 030 031 @SuppressWarnings("unchecked") 032 @Override 033 public List<CounterDataPointSnapshot> getDataPoints() { 034 return (List<CounterDataPointSnapshot>) dataPoints; 035 } 036 037 @SuppressWarnings("unchecked") 038 @Override 039 MetricSnapshot escape( 040 EscapingScheme escapingScheme, List<? extends DataPointSnapshot> dataPointSnapshots) { 041 return new CounterSnapshot( 042 getMetadata().escape(escapingScheme), 043 (List<CounterDataPointSnapshot>) dataPointSnapshots, 044 true); 045 } 046 047 public static class CounterDataPointSnapshot extends DataPointSnapshot { 048 049 private final double value; 050 @Nullable private final Exemplar exemplar; 051 052 /** Optional metric name used only in validation error messages. */ 053 @Nullable private final String metricName; 054 055 /** 056 * To create a new {@link CounterDataPointSnapshot}, you can either call the constructor 057 * directly or use the Builder with {@link CounterDataPointSnapshot#builder()}. 058 * 059 * @param value the counter value. Must not be negative. 060 * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels. 061 * @param exemplar may be null. 062 * @param createdTimestampMillis timestamp (as in {@link System#currentTimeMillis()}) when the 063 * time series (this specific set of labels) was created (or reset to zero). It's optional. 064 * Use {@code 0L} if there is no created timestamp. 065 */ 066 public CounterDataPointSnapshot( 067 double value, Labels labels, @Nullable Exemplar exemplar, long createdTimestampMillis) { 068 this(value, labels, exemplar, createdTimestampMillis, 0, false, null); 069 } 070 071 /** 072 * Constructor with an additional scrape timestamp. This is only useful in rare cases as the 073 * scrape timestamp is usually set by the Prometheus server during scraping. Exceptions include 074 * mirroring metrics with given timestamps from other metric sources. 075 */ 076 @SuppressWarnings("this-escape") 077 public CounterDataPointSnapshot( 078 double value, 079 Labels labels, 080 @Nullable Exemplar exemplar, 081 long createdTimestampMillis, 082 long scrapeTimestampMillis) { 083 this(value, labels, exemplar, createdTimestampMillis, scrapeTimestampMillis, false, null); 084 } 085 086 @SuppressWarnings("this-escape") 087 public CounterDataPointSnapshot( 088 double value, 089 Labels labels, 090 @Nullable Exemplar exemplar, 091 long createdTimestampMillis, 092 long scrapeTimestampMillis, 093 boolean internal) { 094 this(value, labels, exemplar, createdTimestampMillis, scrapeTimestampMillis, internal, null); 095 } 096 097 @SuppressWarnings("this-escape") 098 private CounterDataPointSnapshot( 099 double value, 100 Labels labels, 101 @Nullable Exemplar exemplar, 102 long createdTimestampMillis, 103 long scrapeTimestampMillis, 104 boolean internal, 105 @Nullable String metricName) { 106 super(labels, createdTimestampMillis, scrapeTimestampMillis, internal); 107 this.value = value; 108 this.exemplar = exemplar; 109 this.metricName = metricName; 110 if (!internal) { 111 validate(); 112 } 113 } 114 115 public double getValue() { 116 return value; 117 } 118 119 @Nullable 120 public Exemplar getExemplar() { 121 return exemplar; 122 } 123 124 protected void validate() { 125 if (value < 0.0) { 126 StringBuilder message = new StringBuilder(); 127 if (metricName != null && !metricName.isEmpty()) { 128 message.append(metricName).append('='); 129 } 130 message.append(value).append(": counters cannot have a negative value"); 131 Labels labels = getLabels(); 132 if (labels != null && !labels.isEmpty()) { 133 message.append(" (labels=").append(labels).append(')'); 134 } 135 throw new IllegalArgumentException(message.toString()); 136 } 137 } 138 139 @Override 140 DataPointSnapshot escape(EscapingScheme escapingScheme) { 141 return new CounterSnapshot.CounterDataPointSnapshot( 142 value, 143 SnapshotEscaper.escapeLabels(getLabels(), escapingScheme), 144 SnapshotEscaper.escapeExemplar(exemplar, escapingScheme), 145 getCreatedTimestampMillis(), 146 getScrapeTimestampMillis(), 147 true, 148 metricName); 149 } 150 151 public static Builder builder() { 152 return new Builder(); 153 } 154 155 public static class Builder extends DataPointSnapshot.Builder<Builder> { 156 157 @Nullable private Exemplar exemplar = null; 158 @Nullable private Double value = null; 159 private long createdTimestampMillis = 0L; 160 @Nullable private String metricName = null; 161 162 private Builder() {} 163 164 /** Counter value. This is required. The value must not be negative. */ 165 public Builder value(double value) { 166 this.value = value; 167 return this; 168 } 169 170 public Builder exemplar(@Nullable Exemplar exemplar) { 171 this.exemplar = exemplar; 172 return this; 173 } 174 175 public Builder createdTimestampMillis(long createdTimestampMillis) { 176 this.createdTimestampMillis = createdTimestampMillis; 177 return this; 178 } 179 180 /** 181 * Optional metric name included in the exception message when {@link #value(double)} is 182 * negative. Does not change the snapshot identity. 183 */ 184 public Builder metricName(@Nullable String metricName) { 185 this.metricName = metricName; 186 return this; 187 } 188 189 public CounterDataPointSnapshot build() { 190 if (value == null) { 191 throw new IllegalArgumentException("Missing required field: value is null."); 192 } 193 return new CounterDataPointSnapshot( 194 value, 195 labels, 196 exemplar, 197 createdTimestampMillis, 198 scrapeTimestampMillis, 199 false, 200 metricName); 201 } 202 203 @Override 204 protected Builder self() { 205 return this; 206 } 207 } 208 } 209 210 public static Builder builder() { 211 return new Builder(); 212 } 213 214 public static class Builder extends MetricSnapshot.Builder<Builder> { 215 216 private final List<CounterDataPointSnapshot> dataPoints = new ArrayList<>(); 217 218 private Builder() {} 219 220 /** Add a data point. Can be called multiple times to add multiple data points. */ 221 public Builder dataPoint(CounterDataPointSnapshot dataPoint) { 222 dataPoints.add(dataPoint); 223 return this; 224 } 225 226 @Override 227 protected MetricMetadata buildMetadata() { 228 if (name == null) { 229 throw new IllegalArgumentException("Missing required field: name is null"); 230 } 231 return MetricMetadataSupport.counterMetadata(name, help, unit); 232 } 233 234 @Override 235 public CounterSnapshot build() { 236 return new CounterSnapshot(buildMetadata(), dataPoints); 237 } 238 239 @Override 240 protected Builder self() { 241 return this; 242 } 243 } 244}