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