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}