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