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