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