All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aliyun.openservices.shade.io.prometheus.client.Collector Maven / Gradle / Ivy

There is a newer version: 1.9.4.Final
Show newest version

package com.aliyun.openservices.shade.io.prometheus.client;

import com.aliyun.openservices.shade.io.prometheus.client.exemplars.Exemplar;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
 * A collector for a set of metrics.
 * 

* Normal users should use {@link Gauge}, {@link Counter}, {@link Summary} and {@link Histogram}. *

* Subclasssing Collector is for advanced uses, such as proxying metrics from another monitoring system. * It is it the responsibility of subclasses to ensure they produce valid metrics. * @see Exposition formats. */ public abstract class Collector { /** * Return all metrics of this Collector. */ public abstract List collect(); /** * Like {@link #collect()}, but the result should only contain {@code MetricFamilySamples} where * {@code sampleNameFilter.test(name)} is {@code true} for at least one Sample name. *

* The default implementation first collects all {@code MetricFamilySamples} and then discards the ones * where {@code sampleNameFilter.test(name)} returns {@code false} for all names in * {@link MetricFamilySamples#getNames()}. * To improve performance, collector implementations should override this method to prevent * {@code MetricFamilySamples} from being collected if they will be discarded anyways. * See {@code ThreadExports} for an example. *

* Note that the resulting List may contain {@code MetricFamilySamples} where some Sample names return * {@code true} for {@code sampleNameFilter.test(name)} but some Sample names return {@code false}. * This is ok, because before we produce the output format we will call * {@link MetricFamilySamples#filter(Predicate)} to strip all Samples where {@code sampleNameFilter.test(name)} * returns {@code false}. * * @param sampleNameFilter may be {@code null}, indicating that all metrics should be collected. */ public List collect(Predicate sampleNameFilter) { List all = collect(); if (sampleNameFilter == null) { return all; } List remaining = new ArrayList(all.size()); for (MetricFamilySamples mfs : all) { for (String name : mfs.getNames()) { if (sampleNameFilter.test(name)) { remaining.add(mfs); break; } } } return remaining; } public enum Type { UNKNOWN, // This is untyped in Prometheus text format. COUNTER, GAUGE, STATE_SET, INFO, HISTOGRAM, GAUGE_HISTOGRAM, SUMMARY, } /** * A metric, and all of its samples. */ static public class MetricFamilySamples { public final String name; public final String unit; public final Type type; public final String help; public final List samples; // this list is modified when samples are added/removed. public MetricFamilySamples(String name, Type type, String help, List samples) { this(name, "", type, help, samples); } public MetricFamilySamples(String name, String unit, Type type, String help, List samples) { if (!unit.isEmpty() && !name.endsWith("_" + unit)) { throw new IllegalArgumentException("Metric's unit is not the suffix of the metric name: " + name); } if ((type == Type.INFO || type == Type.STATE_SET) && !unit.isEmpty()) { throw new IllegalArgumentException("Metric is of a type that cannot have a unit: " + name); } List mungedSamples = samples; // Deal with _total from pre-OM automatically. if (type == Type.COUNTER) { if (name.endsWith("_total")) { name = name.substring(0, name.length() - 6); } String withTotal = name + "_total"; mungedSamples = new ArrayList(samples.size()); for (Sample s: samples) { String n = s.name; if (name.equals(n)) { n = withTotal; } mungedSamples.add(new Sample(n, s.labelNames, s.labelValues, s.value, s.exemplar, s.timestampMs)); } } this.name = name; this.unit = unit; this.type = type; this.help = help; this.samples = mungedSamples; } /** * @param sampleNameFilter may be {@code null} indicating that the result contains the complete list of samples. * @return A new MetricFamilySamples containing only the Samples matching the {@code sampleNameFilter}, * or {@code null} if no Sample matches. */ public MetricFamilySamples filter(Predicate sampleNameFilter) { if (sampleNameFilter == null) { return this; } List remainingSamples = new ArrayList(samples.size()); for (Sample sample : samples) { if (sampleNameFilter.test(sample.name)) { remainingSamples.add(sample); } } if (remainingSamples.isEmpty()) { return null; } return new MetricFamilySamples(name, unit, type, help, remainingSamples); } /** * List of names that are reserved for Samples in these MetricsFamilySamples. *

* This is used in two places: *

    *
  1. To check potential name collisions in {@link CollectorRegistry#register(Collector)}. *
  2. To check if a collector may contain metrics matching the metric name filter * in {@link Collector#collect(Predicate)}. *
* Note that {@code getNames()} always includes the name without suffix, even though some * metrics types (like Counter) will not have a Sample with that name. * The reason is that the name without suffix is used in the metadata comments ({@code # TYPE}, {@code # UNIT}, * {@code # HELP}), and as this name must be unique * we include the name without suffix here as well. */ public String[] getNames() { switch (type) { case COUNTER: return new String[]{ name + "_total", name + "_created", name }; case SUMMARY: return new String[]{ name + "_count", name + "_sum", name + "_created", name }; case HISTOGRAM: return new String[]{ name + "_count", name + "_sum", name + "_bucket", name + "_created", name }; case GAUGE_HISTOGRAM: return new String[]{ name + "_gcount", name + "_gsum", name + "_bucket", name }; case INFO: return new String[]{ name + "_info", name }; default: return new String[]{name}; } } @Override public boolean equals(Object obj) { if (!(obj instanceof MetricFamilySamples)) { return false; } MetricFamilySamples other = (MetricFamilySamples) obj; return other.name.equals(name) && other.unit.equals(unit) && other.type.equals(type) && other.help.equals(help) && other.samples.equals(samples); } @Override public int hashCode() { int hash = 1; hash = 37 * hash + name.hashCode(); hash = 37 * hash + unit.hashCode(); hash = 37 * hash + type.hashCode(); hash = 37 * hash + help.hashCode(); hash = 37 * hash + samples.hashCode(); return hash; } @Override public String toString() { return "Name: " + name + " Unit:" + unit + " Type: " + type + " Help: " + help + " Samples: " + samples; } /** * A single Sample, with a unique name and set of labels. */ public static class Sample { public final String name; public final List labelNames; public final List labelValues; // Must have same length as labelNames. public final double value; public final Exemplar exemplar; public final Long timestampMs; // It's an epoch format with milliseconds value included (this field is subject to change). public Sample(String name, List labelNames, List labelValues, double value, Exemplar exemplar, Long timestampMs) { this.name = name; this.labelNames = labelNames; this.labelValues = labelValues; this.value = value; this.exemplar = exemplar; this.timestampMs = timestampMs; } public Sample(String name, List labelNames, List labelValues, double value, Long timestampMs) { this(name, labelNames, labelValues, value, null, timestampMs); } public Sample(String name, List labelNames, List labelValues, double value, Exemplar exemplar) { this(name, labelNames, labelValues, value, exemplar, null); } public Sample(String name, List labelNames, List labelValues, double value) { this(name, labelNames, labelValues, value, null, null); } @Override public boolean equals(Object obj) { if (!(obj instanceof Sample)) { return false; } Sample other = (Sample) obj; return other.name.equals(name) && other.labelNames.equals(labelNames) && other.labelValues.equals(labelValues) && other.value == value && (exemplar == null && other.exemplar == null || other.exemplar != null && other.exemplar.equals(exemplar)) && (timestampMs == null && other.timestampMs == null || other.timestampMs != null && other.timestampMs.equals(timestampMs)); } @Override public int hashCode() { int hash = 1; hash = 37 * hash + name.hashCode(); hash = 37 * hash + labelNames.hashCode(); hash = 37 * hash + labelValues.hashCode(); long d = Double.doubleToLongBits(value); hash = 37 * hash + (int)(d ^ (d >>> 32)); if (timestampMs != null) { hash = 37 * hash + timestampMs.hashCode(); } if (exemplar != null) { hash = 37 * exemplar.hashCode(); } return hash; } @Override public String toString() { return "Name: " + name + " LabelNames: " + labelNames + " labelValues: " + labelValues + " Value: " + value + " TimestampMs: " + timestampMs; } } } /** * Register the Collector with the default registry. */ public T register() { return register(CollectorRegistry.defaultRegistry); } /** * Register the Collector with the given registry. */ public T register(CollectorRegistry registry) { registry.register(this); return (T)this; } public interface Describable { /** * Provide a list of metric families this Collector is expected to return. * * These should exclude the samples. This is used by the registry to * detect collisions and duplicate registrations. * * Usually custom collectors do not have to implement Describable. If * Describable is not implemented and the CollectorRegistry was created * with auto describe enabled (which is the case for the default registry) * then {@link #collect} will be called at registration time instead of * describe. If this could cause problems, either implement a proper * describe, or if that's not practical have describe return an empty * list. */ List describe(); } /* Various utility functions for implementing Collectors. */ /** * Number of nanoseconds in a second. */ public static final double NANOSECONDS_PER_SECOND = 1E9; /** * Number of milliseconds in a second. */ public static final double MILLISECONDS_PER_SECOND = 1E3; private static final Pattern METRIC_NAME_RE = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*"); private static final Pattern METRIC_LABEL_NAME_RE = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"); private static final Pattern RESERVED_METRIC_LABEL_NAME_RE = Pattern.compile("__.*"); /** * Throw an exception if the metric name is invalid. */ protected static void checkMetricName(String name) { if (!METRIC_NAME_RE.matcher(name).matches()) { throw new IllegalArgumentException("Invalid metric name: " + name); } } private static final Pattern SANITIZE_PREFIX_PATTERN = Pattern.compile("^[^a-zA-Z_:]"); private static final Pattern SANITIZE_BODY_PATTERN = Pattern.compile("[^a-zA-Z0-9_:]"); /** * Sanitize metric name */ public static String sanitizeMetricName(String metricName) { return SANITIZE_BODY_PATTERN.matcher( SANITIZE_PREFIX_PATTERN.matcher(metricName).replaceFirst("_") ).replaceAll("_"); } /** * Throw an exception if the metric label name is invalid. */ protected static void checkMetricLabelName(String name) { if (!METRIC_LABEL_NAME_RE.matcher(name).matches()) { throw new IllegalArgumentException("Invalid metric label name: " + name); } if (RESERVED_METRIC_LABEL_NAME_RE.matcher(name).matches()) { throw new IllegalArgumentException("Invalid metric label name, reserved for internal use: " + name); } } /** * Convert a double to its string representation in Go. */ public static String doubleToGoString(double d) { if (d == Double.POSITIVE_INFINITY) { return "+Inf"; } if (d == Double.NEGATIVE_INFINITY) { return "-Inf"; } return Double.toString(d); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy