001package io.prometheus.metrics.core.metrics; 002 003import io.prometheus.metrics.config.MetricsProperties; 004import io.prometheus.metrics.config.PrometheusProperties; 005import io.prometheus.metrics.model.snapshots.Labels; 006import io.prometheus.metrics.model.snapshots.StateSetSnapshot; 007import io.prometheus.metrics.core.datapoints.StateSetDataPoint; 008 009import java.util.ArrayList; 010import java.util.Collections; 011import java.util.List; 012import java.util.stream.Stream; 013 014import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; 015 016/** 017 * StateSet metric. Example: 018 * <pre>{@code 019 * public enum Feature { 020 * 021 * FEATURE_1("feature1"), 022 * FEATURE_2("feature2"); 023 * 024 * private final String name; 025 * 026 * Feature(String name) { 027 * this.name = name; 028 * } 029 * 030 * // Override 031 * public String toString() { 032 * return name; 033 * } 034 * } 035 * 036 * public static void main(String[] args) { 037 * 038 * StateSet stateSet = StateSet.builder() 039 * .name("feature_flags") 040 * .help("Feature flags") 041 * .labelNames("env") 042 * .states(Feature.class) 043 * .register(); 044 * 045 * stateSet.labelValues("dev").setFalse(FEATURE_1); 046 * stateSet.labelValues("dev").setTrue(FEATURE_2); 047 * } 048 * }</pre> 049 * The example above shows how to use a StateSet with an enum. 050 * You don't have to use enum, you can use regular strings as well. 051 */ 052public class StateSet extends StatefulMetric<StateSetDataPoint, StateSet.DataPoint> implements StateSetDataPoint { 053 054 private final boolean exemplarsEnabled; 055 private final String[] names; 056 057 private StateSet(Builder builder, PrometheusProperties prometheusProperties) { 058 super(builder); 059 MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); 060 exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); 061 this.names = builder.names; // builder.names is already a validated copy 062 for (String name : names) { 063 if (this.getMetadata().getPrometheusName().equals(prometheusName(name))) { 064 throw new IllegalArgumentException("Label name " + name + " is illegal (can't use the metric name as label name in state set metrics)"); 065 } 066 } 067 } 068 069 /** 070 * {@inheritDoc} 071 */ 072 @Override 073 public StateSetSnapshot collect() { 074 return (StateSetSnapshot) super.collect(); 075 } 076 077 /** 078 * {@inheritDoc} 079 */ 080 @Override 081 public void setTrue(String state) { 082 getNoLabels().setTrue(state); 083 } 084 085 /** 086 * {@inheritDoc} 087 */ 088 @Override 089 public void setFalse(String state) { 090 getNoLabels().setFalse(state); 091 } 092 093 @Override 094 protected StateSetSnapshot collect(List<Labels> labels, List<DataPoint> metricDataList) { 095 List<StateSetSnapshot.StateSetDataPointSnapshot> data = new ArrayList<>(labels.size()); 096 for (int i = 0; i < labels.size(); i++) { 097 data.add(new StateSetSnapshot.StateSetDataPointSnapshot(names, metricDataList.get(i).values, labels.get(i))); 098 } 099 return new StateSetSnapshot(getMetadata(), data); 100 } 101 102 @Override 103 protected DataPoint newDataPoint() { 104 return new DataPoint(); 105 } 106 107 @Override 108 protected boolean isExemplarsEnabled() { 109 return exemplarsEnabled; 110 } 111 112 class DataPoint implements StateSetDataPoint { 113 114 private final boolean[] values = new boolean[names.length]; 115 116 private DataPoint() { 117 } 118 119 /** 120 * {@inheritDoc} 121 */ 122 @Override 123 public void setTrue(String state) { 124 set(state, true); 125 } 126 127 /** 128 * {@inheritDoc} 129 */ 130 @Override 131 public void setFalse(String state) { 132 set(state, false); 133 } 134 135 private void set(String name, boolean value) { 136 for (int i = 0; i < names.length; i++) { 137 if (names[i].equals(name)) { 138 values[i] = value; 139 return; 140 } 141 } 142 throw new IllegalArgumentException(name + ": unknown state"); 143 } 144 } 145 146 public static Builder builder() { 147 return new Builder(PrometheusProperties.get()); 148 } 149 150 public static Builder builder(PrometheusProperties config) { 151 return new Builder(config); 152 } 153 154 public static class Builder extends StatefulMetric.Builder<Builder, StateSet> { 155 156 private String[] names; 157 158 private Builder(PrometheusProperties config) { 159 super(Collections.emptyList(), config); 160 } 161 162 /** 163 * Declare the states that should be represented by this StateSet. 164 */ 165 public Builder states(Class<? extends Enum<?>> enumClass) { 166 return states(Stream.of(enumClass.getEnumConstants()).map(Enum::toString).toArray(String[]::new)); 167 } 168 169 /** 170 * Declare the states that should be represented by this StateSet. 171 */ 172 public Builder states(String... stateNames) { 173 if (stateNames.length == 0) { 174 throw new IllegalArgumentException("states cannot be empty"); 175 } 176 this.names = Stream.of(stateNames) 177 .distinct() 178 .sorted() 179 .toArray(String[]::new); 180 return this; 181 } 182 183 @Override 184 public StateSet build() { 185 if (names == null) { 186 throw new IllegalStateException("State names are required when building a StateSet."); 187 } 188 return new StateSet(this, properties); 189 } 190 191 @Override 192 protected Builder self() { 193 return this; 194 } 195 } 196}