001package io.prometheus.client;
002
003import io.prometheus.client.CKMSQuantiles.Quantile;
004
005import java.io.Closeable;
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.Collections;
009import java.util.List;
010import java.util.Map;
011import java.util.Set;
012import java.util.LinkedHashSet;
013import java.util.concurrent.Callable;
014import java.util.concurrent.TimeUnit;
015
016/**
017 * Enumeration metric, to track which of a set of states something is in.
018 *
019 * The first provided state will be the default.
020 *
021 * <p>
022 * Example enumeration:
023 * <pre>
024 * {@code
025 *   class YourClass {
026 *     static final Enumeration taskState = Enumeration.build()
027 *         .name("task_state").help("State of the task.")
028 *         .states("stopped", "starting", "running")
029 *         .register();
030 *
031 *     void stop() {
032 *          // Your code here.
033 *          taskState.state("stopped")
034 *     }
035 *   }
036 * }
037 * </pre>
038 *
039 * You can also use a Java Enum:
040 * <pre>
041 *   class YourClass {
042 *     public enum yourEnum {
043 *       STOPPED,
044 *       STARTING,
045 *       RUNNING,
046 *     }
047 *     static final Enumeration taskState = Enumeration.build()
048 *         .name("task_state").help("State of the task.")
049 *         .states(yourEnum.class)
050 *         .register();
051 *
052 *     void stop() {
053 *          // Your code here.
054 *          taskState.state(yourEnum.STOPPED)
055 *     }
056 *   }
057 * }
058 * </pre>
059 */
060public class Enumeration extends SimpleCollector<Enumeration.Child> implements Counter.Describable {
061
062  private final Set<String> states;
063
064  Enumeration(Builder b) {
065    super(b);
066    for (String label : labelNames) {
067      if (label.equals(fullname)) {
068        throw new IllegalStateException("Enumeration cannot have a label named the same as its metric name.");
069      }
070    }
071    states = b.states;
072    initializeNoLabelsChild();
073  }
074
075  public static class Builder extends SimpleCollector.Builder<Builder, Enumeration> {
076
077    private Set<String> states;
078
079    public Builder states(String... s) {
080      if (s.length == 0) {
081        throw new IllegalArgumentException("There must be at least one state");
082      }
083      // LinkedHashSet so we can know which was the first state.
084      states = new LinkedHashSet();
085      states.addAll(Arrays.asList(s));
086      return this;
087    }
088
089    /**
090     * Take states from the names of the values in an Enum class.
091     */
092    public Builder states(Class e) {
093      Object[] vals = e.getEnumConstants();
094      String[] s = new String[vals.length];
095      for(int i = 0; i < vals.length; i++) {
096        s[i] = ((Enum)vals[i]).name();
097      }
098      return states(s);
099    }
100
101    @Override
102    public Enumeration create() {
103      if (states == null) {
104        throw new IllegalStateException("Enumeration states must be specified.");
105      }
106      if (!unit.isEmpty()) {
107        throw new IllegalStateException("Enumeration metrics cannot have a unit.");
108      }
109      dontInitializeNoLabelsChild = true;
110      return new Enumeration(this);
111    }
112  }
113
114  /**
115   *  Return a Builder to allow configuration of a new Enumeration. Ensures required fields are provided.
116   *
117   *  @param name The name of the metric
118   *  @param help The help string of the metric
119   */
120  public static Builder build(String name, String help) {
121    return new Builder().name(name).help(help);
122  }
123
124  /**
125   *  Return a Builder to allow configuration of a new Enumeration.
126   */
127  public static Builder build() {
128    return new Builder();
129  }
130
131  @Override
132  protected Child newChild() {
133    return new Child(states);
134  }
135
136
137  /**
138   * The value of a single Enumeration.
139   * <p>
140   * <em>Warning:</em> References to a Child become invalid after using
141   * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}.
142   */
143  public static class Child {
144
145    private String value;
146    private final Set<String> states;
147
148    private Child(Set<String> states) {
149      this.states = states;
150      value = states.iterator().next(); // Initialize with the first state.
151    }
152
153    /**
154     * Set the state.
155     */
156    public void state(String s) {
157      if (!states.contains(s)) {
158        throw new IllegalArgumentException("Unknown state " + s);
159      }
160      value = s;
161    }
162
163    /**
164     * Set the state.
165     */
166    public void state(Enum e) {
167      state(e.name());
168    }
169
170    /**
171     * Get the state.
172     */
173    public String get() {
174      return value;
175    }
176  }
177
178  // Convenience methods.
179  /**
180   * Set the state on the enum with no labels.
181   */
182  public void state(String s) {
183    noLabelsChild.state(s);
184  }
185
186  /**
187   * Set the state on the enum with no labels.
188   */
189  public void state(Enum e) {
190    noLabelsChild.state(e);
191  }
192
193  /**
194   * Get the value of the Enumeration.
195   */
196  public String get() {
197    return noLabelsChild.get();
198  }
199
200  @Override
201  public List<MetricFamilySamples> collect() {
202    List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>();
203    for(Map.Entry<List<String>, Child> c: children.entrySet()) {
204      String v = c.getValue().get();
205      List<String> labelNamesWithState = new ArrayList<String>(labelNames);
206      labelNamesWithState.add(fullname);
207      for(String s : states) {
208        List<String> labelValuesWithState = new ArrayList<String>(c.getKey());
209        labelValuesWithState.add(s);
210        samples.add(new MetricFamilySamples.Sample(fullname, labelNamesWithState, labelValuesWithState, s.equals(v) ? 1.0 : 0.0));
211      }
212    }
213
214    return familySamplesList(Type.STATE_SET, samples);
215  }
216
217  @Override
218  public List<MetricFamilySamples> describe() {
219    return Collections.singletonList(
220            new MetricFamilySamples(fullname, Type.STATE_SET, help, Collections.<MetricFamilySamples.Sample>emptyList()));
221  }
222
223}