io.prometheus.metrics.model.snapshots.HistogramSnapshot Maven / Gradle / Ivy
Show all versions of jmx_prometheus_httpserver Show documentation
package io.prometheus.metrics.model.snapshots;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* Immutable snapshot of a Histogram.
*/
public final class HistogramSnapshot extends MetricSnapshot {
private final boolean gaugeHistogram;
public static final int CLASSIC_HISTOGRAM = Integer.MIN_VALUE;
/**
* To create a new {@link HistogramSnapshot}, you can either call the constructor directly or use
* the builder with {@link HistogramSnapshot#builder()}.
*
* @param metadata see {@link MetricMetadata} for naming conventions.
* @param data the constructor will create a sorted copy of the collection.
*/
public HistogramSnapshot(MetricMetadata metadata, Collection data) {
this(false, metadata, data);
}
/**
* Use this with the first parameter {@code true} to create a snapshot of a Gauge Histogram.
* The data model for Gauge Histograms is the same as for regular histograms, except that bucket values
* are semantically gauges and not counters.
* See openmetrics.io for more info on Gauge Histograms.
*/
public HistogramSnapshot(boolean isGaugeHistogram, MetricMetadata metadata, Collection data) {
super(metadata, data);
this.gaugeHistogram = isGaugeHistogram;
}
public boolean isGaugeHistogram() {
return gaugeHistogram;
}
@Override
public List getDataPoints() {
return (List) dataPoints;
}
public static final class HistogramDataPointSnapshot extends DistributionDataPointSnapshot {
// There are two types of histograms: Classic histograms and native histograms.
// Classic histograms have a fixed set of buckets.
// Native histograms have "infinitely many" buckets with exponentially growing boundaries.
// The OpenTelemetry terminology for native histogram is "exponential histogram".
// ---
// A histogram can be a classic histogram (indicated by nativeSchema == CLASSIC_HISTOGRAM),
// or a native histogram (indicated by classicBuckets == ClassicHistogramBuckets.EMPTY),
// or both.
// ---
// A histogram that is both classic and native is great for migrating from classic histograms
// to native histograms: Old Prometheus servers can still scrape the classic histogram, while
// new Prometheus servers can scrape the native histogram.
private final ClassicHistogramBuckets classicBuckets; // May be ClassicHistogramBuckets.EMPTY for native histograms.
private final int nativeSchema; // Number in [-4, 8]. May be CLASSIC_HISTOGRAM for classic histograms.
private final long nativeZeroCount; // only used if nativeSchema != CLASSIC_HISTOGRAM
private final double nativeZeroThreshold; // only used if nativeSchema != CLASSIC_HISTOGRAM
private final NativeHistogramBuckets nativeBucketsForPositiveValues; // only used if nativeSchema != CLASSIC_HISTOGRAM
private final NativeHistogramBuckets nativeBucketsForNegativeValues; // only used if nativeSchema != CLASSIC_HISTOGRAM
/**
* Constructor for classic histograms (as opposed to native histograms).
*
* To create a new {@link HistogramDataPointSnapshot}, you can either call the constructor directly or use the
* Builder with {@link HistogramSnapshot#builder()}.
*
* @param classicBuckets required. Must not be empty. Must at least contain the +Inf bucket.
* @param sum sum of all observed values. Optional, pass {@link Double#NaN} if not available.
* @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels.
* @param exemplars must not be null. Use {@link Exemplars#EMPTY} if there are no Exemplars.
* @param createdTimestampMillis timestamp (as in {@link System#currentTimeMillis()}) when the time series
* (this specific set of labels) was created (or reset to zero).
* It's optional. Use {@code 0L} if there is no created timestamp.
*/
public HistogramDataPointSnapshot(
ClassicHistogramBuckets classicBuckets,
double sum,
Labels labels,
Exemplars exemplars,
long createdTimestampMillis) {
this(classicBuckets, CLASSIC_HISTOGRAM, 0, 0, NativeHistogramBuckets.EMPTY, NativeHistogramBuckets.EMPTY, sum, labels, exemplars, createdTimestampMillis, 0L);
}
/**
* Constructor for native histograms (as opposed to classic histograms).
*
* To create a new {@link HistogramDataPointSnapshot}, you can either call the constructor directly or use the
* Builder with {@link HistogramSnapshot#builder()}.
*
* @param nativeSchema number in [-4, 8]. See Prometheus client_model metrics.proto.
* @param nativeZeroCount number of observed zero values (zero is special because there is no
* histogram bucket for zero values).
* @param nativeZeroThreshold observations in [-zeroThreshold, +zeroThreshold] are treated as zero.
* This is to avoid creating a large number of buckets if observations fluctuate around zero.
* @param nativeBucketsForPositiveValues must not be {@code null}. Use {@link NativeHistogramBuckets#EMPTY} if empty.
* @param nativeBucketsForNegativeValues must not be {@code null}. Use {@link NativeHistogramBuckets#EMPTY} if empty.
* @param sum sum of all observed values. Optional, use {@link Double#NaN} if not available.
* @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels.
* @param exemplars must not be null. Use {@link Exemplars#EMPTY} if there are no Exemplars.
* @param createdTimestampMillis timestamp (as in {@link System#currentTimeMillis()}) when the time series
* (this specific set of labels) was created (or reset to zero).
* It's optional. Use {@code 0L} if there is no created timestamp.
*/
public HistogramDataPointSnapshot(
int nativeSchema,
long nativeZeroCount,
double nativeZeroThreshold,
NativeHistogramBuckets nativeBucketsForPositiveValues,
NativeHistogramBuckets nativeBucketsForNegativeValues,
double sum,
Labels labels,
Exemplars exemplars,
long createdTimestampMillis) {
this(ClassicHistogramBuckets.EMPTY, nativeSchema, nativeZeroCount, nativeZeroThreshold, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues, sum, labels, exemplars, createdTimestampMillis, 0L);
}
/**
* Constructor for a histogram with both, classic and native data.
*
* To create a new {@link HistogramDataPointSnapshot}, you can either call the constructor directly or use the
* Builder with {@link HistogramSnapshot#builder()}.
*
* @param classicBuckets required. Must not be empty. Must at least contain the +Inf bucket.
* @param nativeSchema number in [-4, 8]. See Prometheus client_model metrics.proto.
* @param nativeZeroCount number of observed zero values (zero is special because there is no
* histogram bucket for zero values).
* @param nativeZeroThreshold observations in [-zeroThreshold, +zeroThreshold] are treated as zero.
* This is to avoid creating a large number of buckets if observations fluctuate around zero.
* @param nativeBucketsForPositiveValues must not be {@code null}. Use {@link NativeHistogramBuckets#EMPTY} if empty.
* @param nativeBucketsForNegativeValues must not be {@code null}. Use {@link NativeHistogramBuckets#EMPTY} if empty.
* @param sum sum of all observed values. Optional, use {@link Double#NaN} if not available.
* @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels.
* @param exemplars must not be null. Use {@link Exemplars#EMPTY} if there are no Exemplars.
* @param createdTimestampMillis timestamp (as in {@link System#currentTimeMillis()}) when the time series
* (this specific set of labels) was created (or reset to zero).
* It's optional. Use {@code 0L} if there is no created timestamp.
*/
public HistogramDataPointSnapshot(
ClassicHistogramBuckets classicBuckets,
int nativeSchema,
long nativeZeroCount,
double nativeZeroThreshold,
NativeHistogramBuckets nativeBucketsForPositiveValues,
NativeHistogramBuckets nativeBucketsForNegativeValues,
double sum,
Labels labels,
Exemplars exemplars,
long createdTimestampMillis) {
this(classicBuckets, nativeSchema, nativeZeroCount, nativeZeroThreshold, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues, sum, labels, exemplars, createdTimestampMillis, 0L);
}
/**
* Constructor with an additional scrape timestamp.
* This is only useful in rare cases as the scrape timestamp is usually set by the Prometheus server
* during scraping. Exceptions include mirroring metrics with given timestamps from other metric sources.
*/
public HistogramDataPointSnapshot(
ClassicHistogramBuckets classicBuckets,
int nativeSchema,
long nativeZeroCount,
double nativeZeroThreshold,
NativeHistogramBuckets nativeBucketsForPositiveValues,
NativeHistogramBuckets nativeBucketsForNegativeValues,
double sum,
Labels labels,
Exemplars exemplars,
long createdTimestampMillis,
long scrapeTimestampMillis) {
super(calculateCount(classicBuckets, nativeSchema, nativeZeroCount, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues), sum, exemplars, labels, createdTimestampMillis, scrapeTimestampMillis);
this.classicBuckets = classicBuckets;
this.nativeSchema = nativeSchema;
this.nativeZeroCount = nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroCount;
this.nativeZeroThreshold = nativeSchema == CLASSIC_HISTOGRAM ? 0 : nativeZeroThreshold;
this.nativeBucketsForPositiveValues = nativeSchema == CLASSIC_HISTOGRAM ? NativeHistogramBuckets.EMPTY : nativeBucketsForPositiveValues;
this.nativeBucketsForNegativeValues = nativeSchema == CLASSIC_HISTOGRAM ? NativeHistogramBuckets.EMPTY : nativeBucketsForNegativeValues;
validate();
}
private static long calculateCount(ClassicHistogramBuckets classicBuckets, int nativeSchema, long nativeZeroCount, NativeHistogramBuckets nativeBucketsForPositiveValues, NativeHistogramBuckets nativeBucketsForNegativeValues) {
if (classicBuckets.isEmpty()) {
// This is a native histogram
return calculateNativeCount(nativeZeroCount, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues);
} else if (nativeSchema == CLASSIC_HISTOGRAM) {
// This is a classic histogram
return calculateClassicCount(classicBuckets);
} else {
// This is both, a native and a classic histogram. Count should be the same for both.
long classicCount = calculateClassicCount(classicBuckets);
long nativeCount = calculateNativeCount(nativeZeroCount, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues);
if (classicCount != nativeCount) {
throw new IllegalArgumentException("Inconsistent observation count: If a histogram has both classic and native data the observation count must be the same. Classic count is " + classicCount + " but native count is " + nativeCount + ".");
}
return classicCount;
}
}
private static long calculateClassicCount(ClassicHistogramBuckets classicBuckets) {
long count = 0;
for (int i = 0; i < classicBuckets.size(); i++) {
count += classicBuckets.getCount(i);
}
return count;
}
private static long calculateNativeCount(long nativeZeroCount, NativeHistogramBuckets nativeBucketsForPositiveValues, NativeHistogramBuckets nativeBucketsForNegativeValues) {
long count = nativeZeroCount;
for (int i = 0; i < nativeBucketsForNegativeValues.size(); i++) {
count += nativeBucketsForNegativeValues.getCount(i);
}
for (int i = 0; i < nativeBucketsForPositiveValues.size(); i++) {
count += nativeBucketsForPositiveValues.getCount(i);
}
return count;
}
public boolean hasClassicHistogramData() {
return !classicBuckets.isEmpty();
}
public boolean hasNativeHistogramData() {
return nativeSchema != CLASSIC_HISTOGRAM;
}
/**
* Will return garbage if {@link #hasClassicHistogramData()} is {@code false}.
*/
public ClassicHistogramBuckets getClassicBuckets() {
return classicBuckets;
}
/**
* The schema defines the scale of the native histogram, i.g. the granularity of the buckets.
* Current supported values are -4 <= schema <= 8.
* See {@link NativeHistogramBuckets} for more info.
* This will return garbage if {@link #hasNativeHistogramData()} is {@code false}.
*/
public int getNativeSchema() {
return nativeSchema;
}
/**
* Number of observed zero values.
* Will return garbage if {@link #hasNativeHistogramData()} is {@code false}.
*/
public long getNativeZeroCount() {
return nativeZeroCount;
}
/**
* All observations in [-nativeZeroThreshold; +nativeZeroThreshold] are treated as zero.
* This is useful to avoid creation of a large number of buckets if observations fluctuate around zero.
* Will return garbage if {@link #hasNativeHistogramData()} is {@code false}.
*/
public double getNativeZeroThreshold() {
return nativeZeroThreshold;
}
/**
* Will return garbage if {@link #hasNativeHistogramData()} is {@code false}.
*/
public NativeHistogramBuckets getNativeBucketsForPositiveValues() {
return nativeBucketsForPositiveValues;
}
/**
* Will return garbage if {@link #hasNativeHistogramData()} is {@code false}.
*/
public NativeHistogramBuckets getNativeBucketsForNegativeValues() {
return nativeBucketsForNegativeValues;
}
private void validate() {
for (Label label : getLabels()) {
if (label.getName().equals("le")) {
throw new IllegalArgumentException("le is a reserved label name for histograms");
}
}
if (nativeSchema == CLASSIC_HISTOGRAM && classicBuckets.isEmpty()) {
throw new IllegalArgumentException("Histogram buckets cannot be empty, must at least have the +Inf bucket.");
}
if (nativeSchema != CLASSIC_HISTOGRAM) {
if (nativeSchema < -4 || nativeSchema > 8) {
throw new IllegalArgumentException(nativeSchema + ": illegal schema. Expecting number in [-4, 8].");
}
if (nativeZeroCount < 0) {
throw new IllegalArgumentException(nativeZeroCount + ": nativeZeroCount cannot be negative");
}
if (Double.isNaN(nativeZeroThreshold) || nativeZeroThreshold < 0) {
throw new IllegalArgumentException(nativeZeroThreshold + ": illegal nativeZeroThreshold. Must be >= 0.");
}
}
}
public static Builder builder() {
return new Builder();
}
public static class Builder extends DistributionDataPointSnapshot.Builder {
private ClassicHistogramBuckets classicHistogramBuckets = ClassicHistogramBuckets.EMPTY;
private int nativeSchema = CLASSIC_HISTOGRAM;
private long nativeZeroCount = 0;
private double nativeZeroThreshold = 0;
private NativeHistogramBuckets nativeBucketsForPositiveValues = NativeHistogramBuckets.EMPTY;
private NativeHistogramBuckets nativeBucketsForNegativeValues = NativeHistogramBuckets.EMPTY;
private Builder() {
}
@Override
protected Builder self() {
return this;
}
public Builder classicHistogramBuckets(ClassicHistogramBuckets classicBuckets) {
this.classicHistogramBuckets = classicBuckets;
return this;
}
public Builder nativeSchema(int nativeSchema) {
this.nativeSchema = nativeSchema;
return this;
}
public Builder nativeZeroCount(long zeroCount) {
this.nativeZeroCount = zeroCount;
return this;
}
public Builder nativeZeroThreshold(double zeroThreshold) {
this.nativeZeroThreshold = zeroThreshold;
return this;
}
public Builder nativeBucketsForPositiveValues(NativeHistogramBuckets bucketsForPositiveValues) {
this.nativeBucketsForPositiveValues = bucketsForPositiveValues;
return this;
}
public Builder nativeBucketsForNegativeValues(NativeHistogramBuckets bucketsForNegativeValues) {
this.nativeBucketsForNegativeValues = bucketsForNegativeValues;
return this;
}
public HistogramDataPointSnapshot build() {
if (nativeSchema == CLASSIC_HISTOGRAM && classicHistogramBuckets.isEmpty()) {
throw new IllegalArgumentException("One of nativeSchema and classicHistogramBuckets is required.");
}
return new HistogramDataPointSnapshot(classicHistogramBuckets, nativeSchema, nativeZeroCount, nativeZeroThreshold, nativeBucketsForPositiveValues, nativeBucketsForNegativeValues, sum, labels, exemplars, createdTimestampMillis, scrapeTimestampMillis);
}
}
}
public static Builder builder() {
return new Builder();
}
public static class Builder extends MetricSnapshot.Builder {
private final List dataPoints = new ArrayList<>();
private boolean isGaugeHistogram = false;
private Builder() {
}
/**
* Add a data point. Call multiple times to add multiple data points.
*/
public Builder dataPoint(HistogramDataPointSnapshot dataPoint) {
dataPoints.add(dataPoint);
return this;
}
/**
* {@code true} indicates that this histogram is a gauge histogram.
* The data model for gauge histograms is the same as for regular histograms,
* except that bucket values are semantically gauges and not counters.
* See openmetrics.io for more info on gauge histograms.
*/
public Builder gaugeHistogram(boolean isGaugeHistogram) {
this.isGaugeHistogram = isGaugeHistogram;
return this;
}
public HistogramSnapshot build() {
return new HistogramSnapshot(isGaugeHistogram, buildMetadata(), dataPoints);
}
@Override
protected Builder self() {
return this;
}
}
}