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