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