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}