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 java.util.stream.Stream; 010 011/** 012 * Immutable snapshot of a StateSet metric. 013 */ 014public final class StateSetSnapshot extends MetricSnapshot { 015 016 /** 017 * To create a new {@link StateSetSnapshot}, you can either call the constructor directly or use 018 * the builder with {@link StateSetSnapshot#builder()}. 019 * 020 * @param metadata See {@link MetricMetadata} for more naming conventions. 021 * @param data the constructor will create a sorted copy of the collection. 022 */ 023 public StateSetSnapshot(MetricMetadata metadata, Collection<StateSetDataPointSnapshot> data) { 024 super(metadata, data); 025 validate(); 026 } 027 028 private void validate() { 029 if (getMetadata().hasUnit()) { 030 throw new IllegalArgumentException("An state set metric cannot have a unit."); 031 } 032 for (StateSetDataPointSnapshot entry : getDataPoints()) { 033 if (entry.getLabels().contains(getMetadata().getPrometheusName())) { 034 throw new IllegalArgumentException("Label name " + getMetadata().getPrometheusName() + " is reserved."); 035 } 036 } 037 } 038 039 @Override 040 public List<StateSetDataPointSnapshot> getDataPoints() { 041 return (List<StateSetDataPointSnapshot>) dataPoints; 042 } 043 044 045 public static class StateSetDataPointSnapshot extends DataPointSnapshot implements Iterable<State> { 046 private final String[] names; 047 private final boolean[] values; 048 049 /** 050 * To create a new {@link StateSetDataPointSnapshot}, you can either call the constructor directly or use the 051 * Builder with {@link StateSetDataPointSnapshot#builder()}. 052 * 053 * @param names state names. Must have at least 1 entry. 054 * The constructor will create a copy of the array. 055 * @param values state values. Must have the same length as {@code names}. 056 * The constructor will create a copy of the array. 057 * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels. 058 */ 059 public StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels) { 060 this(names, values, labels, 0L); 061 } 062 063 /** 064 * Constructor with an additional scrape timestamp. 065 * This is only useful in rare cases as the scrape timestamp is usually set by the Prometheus server 066 * during scraping. Exceptions include mirroring metrics with given timestamps from other metric sources. 067 */ 068 public StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels, long scrapeTimestampMillis) { 069 super(labels, 0L, scrapeTimestampMillis); 070 if (names.length == 0) { 071 throw new IllegalArgumentException("StateSet must have at least one state."); 072 } 073 if (names.length != values.length) { 074 throw new IllegalArgumentException("names[] and values[] must have the same length"); 075 } 076 String[] namesCopy = Arrays.copyOf(names, names.length); 077 boolean[] valuesCopy = Arrays.copyOf(values, names.length); 078 sort(namesCopy, valuesCopy); 079 this.names = namesCopy; 080 this.values = valuesCopy; 081 validate(); 082 } 083 084 public int size() { 085 return names.length; 086 } 087 088 public String getName(int i) { 089 return names[i]; 090 } 091 092 public boolean isTrue(int i) { 093 return values[i]; 094 } 095 096 private void validate() { 097 for (int i = 0; i < names.length; i++) { 098 if (names[i].length() == 0) { 099 throw new IllegalArgumentException("Empty string as state name"); 100 } 101 if (i > 0 && names[i - 1].equals(names[i])) { 102 throw new IllegalArgumentException(names[i] + " duplicate state name"); 103 } 104 } 105 } 106 107 private List<State> asList() { 108 List<State> result = new ArrayList<>(size()); 109 for (int i = 0; i < names.length; i++) { 110 result.add(new State(names[i], values[i])); 111 } 112 return Collections.unmodifiableList(result); 113 } 114 115 @Override 116 public Iterator<State> iterator() { 117 return asList().iterator(); 118 } 119 120 public Stream<State> stream() { 121 return asList().stream(); 122 } 123 124 private static void sort(String[] names, boolean[] values) { 125 // Bubblesort 126 int n = names.length; 127 for (int i = 0; i < n - 1; i++) { 128 for (int j = 0; j < n - i - 1; j++) { 129 if (names[j].compareTo(names[j + 1]) > 0) { 130 swap(j, j + 1, names, values); 131 } 132 } 133 } 134 } 135 136 private static void swap(int i, int j, String[] names, boolean[] values) { 137 String tmpName = names[j]; 138 names[j] = names[i]; 139 names[i] = tmpName; 140 boolean tmpValue = values[j]; 141 values[j] = values[i]; 142 values[i] = tmpValue; 143 } 144 145 public static Builder builder() { 146 return new Builder(); 147 } 148 149 public static class Builder extends DataPointSnapshot.Builder<Builder> { 150 151 private final ArrayList<String> names = new ArrayList<>(); 152 private final ArrayList<Boolean> values = new ArrayList<>(); 153 154 private Builder() {} 155 156 /** 157 * Add a state. Call multple times to add multiple states. 158 */ 159 public Builder state(String name, boolean value) { 160 names.add(name); 161 values.add(value); 162 return this; 163 } 164 165 @Override 166 protected Builder self() { 167 return this; 168 } 169 170 public StateSetDataPointSnapshot build() { 171 boolean[] valuesArray = new boolean[values.size()]; 172 for (int i = 0; i < values.size(); i++) { 173 valuesArray[i] = values.get(i); 174 } 175 return new StateSetDataPointSnapshot(names.toArray(new String[]{}), valuesArray, labels, scrapeTimestampMillis); 176 } 177 } 178 } 179 180 public static class State { 181 private final String name; 182 private final boolean value; 183 184 private State(String name, boolean value) { 185 this.name = name; 186 this.value = value; 187 } 188 189 public String getName() { 190 return name; 191 } 192 193 public boolean isTrue() { 194 return value; 195 } 196 } 197 198 public static Builder builder() { 199 return new Builder(); 200 } 201 202 public static class Builder extends MetricSnapshot.Builder<Builder> { 203 204 private final List<StateSetDataPointSnapshot> dataPoints = new ArrayList<>(); 205 206 private Builder() { 207 } 208 209 /** 210 * Add a data point. Call multiple times to add multiple data points. 211 */ 212 public Builder dataPoint(StateSetDataPointSnapshot dataPoint) { 213 dataPoints.add(dataPoint); 214 return this; 215 } 216 217 @Override 218 public Builder unit(Unit unit) { 219 throw new IllegalArgumentException("StateSet metric cannot have a unit."); 220 } 221 222 @Override 223 public StateSetSnapshot build() { 224 return new StateSetSnapshot(buildMetadata(), dataPoints); 225 } 226 227 @Override 228 protected Builder self() { 229 return this; 230 } 231 } 232}