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