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      public final Long timestampMs;  // It's an epoch format with milliseconds value included (this field is subject to change).
081
082      public Sample(String name, List<String> labelNames, List<String> labelValues, double value, Long timestampMs) {
083        this.name = name;
084        this.labelNames = labelNames;
085        this.labelValues = labelValues;
086        this.value = value;
087        this.timestampMs = timestampMs;
088      }
089
090      public Sample(String name, List<String> labelNames, List<String> labelValues, double value) {
091          this(name, labelNames, labelValues, value, null);
092      }
093
094      @Override
095      public boolean equals(Object obj) {
096        if (!(obj instanceof Sample)) {
097          return false;
098        }
099        Sample other = (Sample) obj;
100
101        return other.name.equals(name) && other.labelNames.equals(labelNames)
102          && other.labelValues.equals(labelValues) && other.value == value
103          && ((timestampMs == null && other.timestampMs == null) || (other.timestampMs != null) && (other.timestampMs.equals(timestampMs)));
104      }
105
106      @Override
107      public int hashCode() {
108        int hash = 1;
109        hash = 37 * hash + name.hashCode();
110        hash = 37 * hash + labelNames.hashCode();
111        hash = 37 * hash + labelValues.hashCode();
112        long d = Double.doubleToLongBits(value);
113        hash = 37 * hash + (int)(d ^ (d >>> 32));
114        if (timestampMs != null) {
115          hash = 37 * hash + timestampMs.hashCode();
116        }
117        return hash;
118      }
119
120      @Override
121      public String toString() {
122        return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues +
123          " Value: " + value + " TimestampMs: " + timestampMs;
124      }
125    }
126  }
127
128  /**
129   * Register the Collector with the default registry.
130   */
131  public <T extends Collector> T register() {
132    return register(CollectorRegistry.defaultRegistry);
133  }
134
135  /**
136   * Register the Collector with the given registry.
137   */
138  public <T extends Collector> T register(CollectorRegistry registry) {
139    registry.register(this);
140    return (T)this;
141  }
142
143  public interface Describable {
144    /**
145     *  Provide a list of metric families this Collector is expected to return.
146     *
147     *  These should exclude the samples. This is used by the registry to
148     *  detect collisions and duplicate registrations.
149     *
150     *  Usually custom collectors do not have to implement Describable. If
151     *  Describable is not implemented and the CollectorRegistry was created
152     *  with auto describe enabled (which is the case for the default registry)
153     *  then {@link collect} will be called at registration time instead of
154     *  describe. If this could cause problems, either implement a proper
155     *  describe, or if that's not practical have describe return an empty
156     *  list.
157     */
158    List<MetricFamilySamples> describe();
159  }
160
161
162  /* Various utility functions for implementing Collectors. */
163
164  /**
165   * Number of nanoseconds in a second.
166   */
167  public static final double NANOSECONDS_PER_SECOND = 1E9;
168  /**
169   * Number of milliseconds in a second.
170   */
171  public static final double MILLISECONDS_PER_SECOND = 1E3;
172
173  private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*");
174  private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*");
175  private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*");
176
177  /**
178   * Throw an exception if the metric name is invalid.
179   */
180  protected static void checkMetricName(String name) {
181    if (!METRIC_NAME_RE.matcher(name).matches()) {
182      throw new IllegalArgumentException("Invalid metric name: " + name);
183    }
184  }
185
186  private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_]");
187  private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_]");
188
189  /**
190   * Sanitize metric name
191   */
192  public static String sanitizeMetricName(String metricName) {
193    return SANITIZE_BODY_PATTERN.matcher(
194            SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_")
195    ).replaceAll("_");
196  }
197
198  /**
199   * Throw an exception if the metric label name is invalid.
200   */
201  protected static void checkMetricLabelName(String name) {
202    if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) {
203      throw new IllegalArgumentException("Invalid metric label name: " + name);
204    }
205    if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) {
206      throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name);
207    }
208  }
209
210  /**
211   * Convert a double to its string representation in Go.
212   */
213  public static String doubleToGoString(double d) {
214    if (d == Double.POSITIVE_INFINITY) {
215      return "+Inf";
216    } 
217    if (d == Double.NEGATIVE_INFINITY) {
218      return "-Inf";
219    }
220    if (Double.isNaN(d)) {
221      return "NaN";
222    }
223    return Double.toString(d);
224  }
225}