001package io.prometheus.metrics.model.snapshots;
002
003import io.prometheus.metrics.annotations.StableApi;
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Iterator;
009import java.util.List;
010import javax.annotation.Nullable;
011
012/**
013 * Immutable container for Exemplars.
014 *
015 * <p>This is currently backed by a {@code List<Exemplar>}. May be refactored later to use a more
016 * efficient data structure.
017 */
018@StableApi
019public class Exemplars implements Iterable<Exemplar> {
020
021  /** EMPTY means no Exemplars. */
022  public static final Exemplars EMPTY = new Exemplars(Collections.emptyList());
023
024  private final List<Exemplar> exemplars;
025
026  private Exemplars(Collection<Exemplar> exemplars) {
027    List<Exemplar> copy = new ArrayList<>(exemplars.size());
028    for (Exemplar exemplar : exemplars) {
029      if (exemplar == null) {
030        throw new NullPointerException("Illegal null value in Exemplars");
031      }
032      copy.add(exemplar);
033    }
034    this.exemplars = Collections.unmodifiableList(copy);
035  }
036
037  /**
038   * Create a new Exemplars instance. You can either create Exemplars with one of the static {@code
039   * Exemplars.of(...)} methods, or you can use the {@link Exemplars#builder()}.
040   *
041   * @param exemplars a copy of the exemplars collection will be created.
042   */
043  public static Exemplars of(Collection<Exemplar> exemplars) {
044    return new Exemplars(exemplars);
045  }
046
047  /**
048   * Create a new Exemplars instance. You can either create Exemplars with one of the static {@code
049   * Exemplars.of(...)} methods, or you can use the {@link Exemplars#builder()}.
050   *
051   * @param exemplars a copy of the exemplars array will be created.
052   */
053  public static Exemplars of(Exemplar... exemplars) {
054    return new Exemplars(Arrays.asList(exemplars));
055  }
056
057  @Override
058  public Iterator<Exemplar> iterator() {
059    return exemplars.iterator();
060  }
061
062  public int size() {
063    return exemplars.size();
064  }
065
066  @Nullable
067  public Exemplar get(int index) {
068    return exemplars.get(index);
069  }
070
071  /**
072   * This is used by classic histograms to find an exemplar with a value between lowerBound and
073   * upperBound. If there is more than one exemplar within the bounds the one with the newest time
074   * stamp is returned.
075   */
076  @Nullable
077  public Exemplar get(double lowerBound, double upperBound) {
078    Exemplar result = null;
079    for (Exemplar exemplar : exemplars) {
080      double value = exemplar.getValue();
081      if (value > lowerBound && value <= upperBound) {
082        if (result == null) {
083          result = exemplar;
084        } else if (result.hasTimestamp() && exemplar.hasTimestamp()) {
085          if (exemplar.getTimestampMillis() > result.getTimestampMillis()) {
086            result = exemplar;
087          }
088        }
089      }
090    }
091    return result;
092  }
093
094  /** Find the Exemplar with the newest timestamp. May return {@code null}. */
095  @Nullable
096  public Exemplar getLatest() {
097    Exemplar latest = null;
098    for (Exemplar candidate : exemplars) {
099      if (candidate == null) {
100        continue;
101      }
102      if (latest == null) {
103        latest = candidate;
104        continue;
105      }
106      if (!latest.hasTimestamp()) {
107        latest = candidate;
108        continue;
109      }
110      if (candidate.hasTimestamp()) {
111        if (latest.getTimestampMillis() < candidate.getTimestampMillis()) {
112          latest = candidate;
113        }
114      }
115    }
116    return latest;
117  }
118
119  public static Builder builder() {
120    return new Builder();
121  }
122
123  public static class Builder {
124
125    private final ArrayList<Exemplar> exemplars = new ArrayList<>();
126
127    private Builder() {}
128
129    /** Add an exemplar. This can be called multiple times to add multiple exemplars. */
130    public Builder exemplar(Exemplar exemplar) {
131      exemplars.add(exemplar);
132      return this;
133    }
134
135    /** Add all exemplars form the collection. */
136    public Builder exemplars(Collection<Exemplar> exemplars) {
137      this.exemplars.addAll(exemplars);
138      return this;
139    }
140
141    public Exemplars build() {
142      return Exemplars.of(exemplars);
143    }
144  }
145}