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    /**
053     * To create a new {@link CounterDataPointSnapshot}, you can either call the constructor
054     * directly or use the Builder with {@link CounterDataPointSnapshot#builder()}.
055     *
056     * @param value the counter value. Must not be negative.
057     * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels.
058     * @param exemplar may be null.
059     * @param createdTimestampMillis timestamp (as in {@link System#currentTimeMillis()}) when the
060     *     time series (this specific set of labels) was created (or reset to zero). It's optional.
061     *     Use {@code 0L} if there is no created timestamp.
062     */
063    public CounterDataPointSnapshot(
064        double value, Labels labels, @Nullable Exemplar exemplar, long createdTimestampMillis) {
065      this(value, labels, exemplar, createdTimestampMillis, 0);
066    }
067
068    /**
069     * Constructor with an additional scrape timestamp. This is only useful in rare cases as the
070     * scrape timestamp is usually set by the Prometheus server during scraping. Exceptions include
071     * mirroring metrics with given timestamps from other metric sources.
072     */
073    @SuppressWarnings("this-escape")
074    public CounterDataPointSnapshot(
075        double value,
076        Labels labels,
077        @Nullable Exemplar exemplar,
078        long createdTimestampMillis,
079        long scrapeTimestampMillis) {
080      this(value, labels, exemplar, createdTimestampMillis, scrapeTimestampMillis, false);
081    }
082
083    @SuppressWarnings("this-escape")
084    public CounterDataPointSnapshot(
085        double value,
086        Labels labels,
087        @Nullable Exemplar exemplar,
088        long createdTimestampMillis,
089        long scrapeTimestampMillis,
090        boolean internal) {
091      super(labels, createdTimestampMillis, scrapeTimestampMillis, internal);
092      this.value = value;
093      this.exemplar = exemplar;
094      if (!internal) {
095        validate();
096      }
097    }
098
099    public double getValue() {
100      return value;
101    }
102
103    @Nullable
104    public Exemplar getExemplar() {
105      return exemplar;
106    }
107
108    protected void validate() {
109      if (value < 0.0) {
110        throw new IllegalArgumentException(value + ": counters cannot have a negative value");
111      }
112    }
113
114    @Override
115    DataPointSnapshot escape(EscapingScheme escapingScheme) {
116      return new CounterSnapshot.CounterDataPointSnapshot(
117          value,
118          SnapshotEscaper.escapeLabels(getLabels(), escapingScheme),
119          SnapshotEscaper.escapeExemplar(exemplar, escapingScheme),
120          getCreatedTimestampMillis(),
121          getScrapeTimestampMillis(),
122          true);
123    }
124
125    public static Builder builder() {
126      return new Builder();
127    }
128
129    public static class Builder extends DataPointSnapshot.Builder<Builder> {
130
131      @Nullable private Exemplar exemplar = null;
132      @Nullable private Double value = null;
133      private long createdTimestampMillis = 0L;
134
135      private Builder() {}
136
137      /** Counter value. This is required. The value must not be negative. */
138      public Builder value(double value) {
139        this.value = value;
140        return this;
141      }
142
143      public Builder exemplar(@Nullable Exemplar exemplar) {
144        this.exemplar = exemplar;
145        return this;
146      }
147
148      public Builder createdTimestampMillis(long createdTimestampMillis) {
149        this.createdTimestampMillis = createdTimestampMillis;
150        return this;
151      }
152
153      public CounterDataPointSnapshot build() {
154        if (value == null) {
155          throw new IllegalArgumentException("Missing required field: value is null.");
156        }
157        return new CounterDataPointSnapshot(
158            value, labels, exemplar, createdTimestampMillis, scrapeTimestampMillis);
159      }
160
161      @Override
162      protected Builder self() {
163        return this;
164      }
165    }
166  }
167
168  public static Builder builder() {
169    return new Builder();
170  }
171
172  public static class Builder extends MetricSnapshot.Builder<Builder> {
173
174    private final List<CounterDataPointSnapshot> dataPoints = new ArrayList<>();
175
176    private Builder() {}
177
178    /** Add a data point. Can be called multiple times to add multiple data points. */
179    public Builder dataPoint(CounterDataPointSnapshot dataPoint) {
180      dataPoints.add(dataPoint);
181      return this;
182    }
183
184    @Override
185    protected MetricMetadata buildMetadata() {
186      if (name == null) {
187        throw new IllegalArgumentException("Missing required field: name is null");
188      }
189      return MetricMetadataSupport.counterMetadata(name, help, unit);
190    }
191
192    @Override
193    public CounterSnapshot build() {
194      return new CounterSnapshot(buildMetadata(), dataPoints);
195    }
196
197    @Override
198    protected Builder self() {
199      return this;
200    }
201  }
202}