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