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