001
002package io.prometheus.client;
003
004import java.util.List;
005import java.util.regex.Pattern;
006
007/**
008 * A collector for a set of metrics.
009 * <p>
010 * Normal users should use {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}.
011 * <p>
012 * Subclasssing Collector is for advanced uses, such as proxying metrics from another monitoring system.
013 * It is it the responsibility of subclasses to ensure they produce valid metrics.
014 * @see <a href="http://prometheus.io/docs/instrumenting/exposition_formats/">Exposition formats</a>.
015 */
016public abstract class Collector {
017  /**
018   * Return all of the metrics of this Collector.
019   */
020  public abstract List<MetricFamilySamples> collect();
021  public enum Type {
022    COUNTER,
023    GAUGE,
024    SUMMARY,
025    HISTOGRAM,
026    UNTYPED,
027  }
028
029  /**
030   * A metric, and all of its samples.
031   */
032  static public class MetricFamilySamples {
033    public final String name;
034    public final Type type;
035    public final String help;
036    public final List<Sample> samples;
037
038    public MetricFamilySamples(String name, Type type, String help, List<Sample> samples) {
039      this.name = name;
040      this.type = type;
041      this.help = help;
042      this.samples = samples;
043    }
044
045    @Override
046    public boolean equals(Object obj) {
047      if (!(obj instanceof MetricFamilySamples)) {
048        return false;
049      }
050      MetricFamilySamples other = (MetricFamilySamples) obj;
051      
052      return other.name.equals(name) && other.type.equals(type)
053        && other.help.equals(help) && other.samples.equals(samples) ;
054    }
055
056    @Override
057    public int hashCode() {
058      int hash = 1;
059      hash = 37 * hash + name.hashCode();
060      hash = 37 * hash + type.hashCode();
061      hash = 37 * hash + help.hashCode();
062      hash = 37 * hash + samples.hashCode();
063      return hash;
064    }
065
066    @Override
067    public String toString() {
068      return "Name: " + name + " Type: " + type + " Help: " + help + 
069        " Samples: " + samples;
070    }
071
072  /**
073   * A single Sample, with a unique name and set of labels.
074   */
075    public static class Sample {
076      public final String name;
077      public final List<String> labelNames;
078      public final List<String> labelValues;  // Must have same length as labelNames.
079      public final double value;
080
081      public Sample(String name, List<String> labelNames, List<String> labelValues, double value) {
082        this.name = name;
083        this.labelNames = labelNames;
084        this.labelValues = labelValues;
085        this.value = value;
086      }
087
088      @Override
089      public boolean equals(Object obj) {
090        if (!(obj instanceof Sample)) {
091          return false;
092        }
093        Sample other = (Sample) obj;
094        return other.name.equals(name) && other.labelNames.equals(labelNames)
095          && other.labelValues.equals(labelValues) && other.value == value;
096      }
097
098      @Override
099      public int hashCode() {
100        int hash = 1;
101        hash = 37 * hash + name.hashCode();
102        hash = 37 * hash + labelNames.hashCode();
103        hash = 37 * hash + labelValues.hashCode();
104        long d = Double.doubleToLongBits(value);
105        hash = 37 * hash + (int)(d ^ (d >>> 32));
106        return hash;
107      }
108
109      @Override
110      public String toString() {
111        return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues +
112          " Value: " + value;
113      }
114    }
115  }
116
117  /**
118   * Register the Collector with the default registry.
119   */
120  public <T extends Collector> T register() {
121    return register(CollectorRegistry.defaultRegistry);
122  }
123
124  /**
125   * Register the Collector with the given registry.
126   */
127  public <T extends Collector> T register(CollectorRegistry registry) {
128    registry.register(this);
129    return (T)this;
130  }
131
132  public interface Describable {
133    /**
134     *  Provide a list of metric families this Collector is expected to return.
135     *
136     *  These should exclude the samples. This is used by the registry to
137     *  detect collisions and duplicate registrations.
138     *
139     *  Usually custom collectors do not have to implement Describable. If
140     *  Describable is not implemented and the CollectorRegistry was created
141     *  with auto describe enabled (which is the case for the default registry)
142     *  then {@link collect} will be called at registration time instead of
143     *  describe. If this could cause problems, either implement a proper
144     *  describe, or if that's not practical have describe return an empty
145     *  list.
146     */
147    List<MetricFamilySamples> describe();
148  }
149
150
151  /* Various utility functions for implementing Collectors. */
152
153  /**
154   * Number of nanoseconds in a second.
155   */
156  public static final double NANOSECONDS_PER_SECOND = 1E9;
157  /**
158   * Number of milliseconds in a second.
159   */
160  public static final double MILLISECONDS_PER_SECOND = 1E3;
161
162  private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*");
163  private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
164  private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*");
165
166  /**
167   * Throw an exception if the metric name is invalid.
168   */
169  protected static void checkMetricName(String name) {
170    if (!METRIC_NAME_RE.matcher(name).matches()) {
171      throw new IllegalArgumentException("Invalid metric name: " + name);
172    }
173  }
174
175  private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_]");
176  private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_]");
177
178  /**
179   * Sanitize metric name
180   */
181  public static String sanitizeMetricName(String metricName) {
182    return SANITIZE_BODY_PATTERN.matcher(
183            SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_")
184    ).replaceAll("_");
185  }
186
187  /**
188   * Throw an exception if the metric label name is invalid.
189   */
190  protected static void checkMetricLabelName(String name) {
191    if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) {
192      throw new IllegalArgumentException("Invalid metric label name: " + name);
193    }
194    if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) {
195      throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name);
196    }
197  }
198
199  /**
200   * Convert a double to its string representation in Go.
201   */
202  public static String doubleToGoString(double d) {
203    if (d == Double.POSITIVE_INFINITY) {
204      return "+Inf";
205    } 
206    if (d == Double.NEGATIVE_INFINITY) {
207      return "-Inf";
208    }
209    if (Double.isNaN(d)) {
210      return "NaN";
211    }
212    return Double.toString(d);
213  }
214}