diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bbe74bc2..1e1eb867c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,3 +10,9 @@ Prometheus uses GitHub to manage reviews of pull requests. on our [mailing list](https://groups.google.com/forum/?fromgroups#!forum/prometheus-developers). This will avoid unnecessary work and surely give you and us a good deal of inspiration. + +## Formatting + +This repository uses [Google Java Format](https://github.com/google/google-java-format) to format the code. + +Run `./mvnw spotless:apply` to format the code (only changed files) before committing. diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java index 0c69a89a9..57b2b640f 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/Collector.java @@ -1,76 +1,77 @@ package io.prometheus.metrics.model.registry; import io.prometheus.metrics.model.snapshots.MetricSnapshot; - import java.util.function.Predicate; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; - /** - * To be registered with the Prometheus collector registry. - * See Overall Structure on - * https://prometheus.io/docs/instrumenting/writing_clientlibs/. + * To be registered with the Prometheus collector registry. See Overall Structure on https://prometheus.io/docs/instrumenting/writing_clientlibs/. */ @FunctionalInterface public interface Collector { - /** - * Called when the Prometheus server scrapes metrics. - */ - MetricSnapshot collect(); + /** Called when the Prometheus server scrapes metrics. */ + MetricSnapshot collect(); - /** - * Provides Collector with the details of the request issued by Prometheus to allow multi-target pattern implementation - * Override to implement request dependent logic to provide MetricSnapshot - */ - default MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) { - return collect(); - } - - /** - * Like {@link #collect()}, but returns {@code null} if {@code includedNames.test(name)} is {@code false}. - *

- * Override this if there is a more efficient way than first collecting the snapshot and then discarding it. - */ - default MetricSnapshot collect(Predicate includedNames) { - MetricSnapshot result = collect(); - if (includedNames.test(result.getMetadata().getPrometheusName())) { - return result; - } else { - return null; - } - } - - /** - * Like {@link #collect(Predicate)}, but with support for multi-target pattern. - *

- * Override this if there is a more efficient way than first collecting the snapshot and then discarding it. - */ - default MetricSnapshot collect(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { - MetricSnapshot result = collect(scrapeRequest); - if (includedNames.test(result.getMetadata().getPrometheusName())) { - return result; - } else { - return null; - } + /** + * Provides Collector with the details of the request issued by Prometheus to allow multi-target + * pattern implementation Override to implement request dependent logic to provide MetricSnapshot + */ + default MetricSnapshot collect(PrometheusScrapeRequest scrapeRequest) { + return collect(); + } + + /** + * Like {@link #collect()}, but returns {@code null} if {@code includedNames.test(name)} is {@code + * false}. + * + *

Override this if there is a more efficient way than first collecting the snapshot and then + * discarding it. + */ + default MetricSnapshot collect(Predicate includedNames) { + MetricSnapshot result = collect(); + if (includedNames.test(result.getMetadata().getPrometheusName())) { + return result; + } else { + return null; } - + } - /** - * This is called in two places: - *

    - *
  1. During registration to check if a metric with that name already exists.
  2. - *
  3. During scrape to check if this collector can be skipped because a name filter is present and the metric name is excluded.
  4. - *
- * Returning {@code null} means checks are omitted (registration the metric always succeeds), - * and the collector is always scraped (the result is dropped after scraping if a name filter is present and - * the metric name is excluded). - *

- * If your metric has a name that does not change at runtime it is a good idea to overwrite this and return the name. - *

- * All metrics in {@code prometheus-metrics-core} override this. - */ - default String getPrometheusName() { - return null; + /** + * Like {@link #collect(Predicate)}, but with support for multi-target pattern. + * + *

Override this if there is a more efficient way than first collecting the snapshot and then + * discarding it. + */ + default MetricSnapshot collect( + Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + MetricSnapshot result = collect(scrapeRequest); + if (includedNames.test(result.getMetadata().getPrometheusName())) { + return result; + } else { + return null; } + } + + /** + * This is called in two places: + * + *

    + *
  1. During registration to check if a metric with that name already exists. + *
  2. During scrape to check if this collector can be skipped because a name filter is present + * and the metric name is excluded. + *
+ * + * Returning {@code null} means checks are omitted (registration the metric always succeeds), and + * the collector is always scraped (the result is dropped after scraping if a name filter is + * present and the metric name is excluded). + * + *

If your metric has a name that does not change at runtime it is a good idea to overwrite + * this and return the name. + * + *

All metrics in {@code prometheus-metrics-core} override this. + */ + default String getPrometheusName() { + return null; + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java index 8b059adb3..00a19d9f6 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java @@ -2,128 +2,143 @@ import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; +import io.prometheus.metrics.model.snapshots.MetricSnapshot; +import io.prometheus.metrics.model.snapshots.MetricSnapshots; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; -import io.prometheus.metrics.model.snapshots.MetricSnapshots; - public class PrometheusRegistry { - public static final PrometheusRegistry defaultRegistry = new PrometheusRegistry(); - - private final Set prometheusNames = ConcurrentHashMap.newKeySet(); - private final List collectors = new CopyOnWriteArrayList<>(); - private final List multiCollectors = new CopyOnWriteArrayList<>(); + public static final PrometheusRegistry defaultRegistry = new PrometheusRegistry(); - public void register(Collector collector) { - String prometheusName = collector.getPrometheusName(); - if (prometheusName != null) { - if (!prometheusNames.add(prometheusName)) { - throw new IllegalStateException("Can't register " + prometheusName + " because a metric with that name is already registered."); - } - } - collectors.add(collector); - } + private final Set prometheusNames = ConcurrentHashMap.newKeySet(); + private final List collectors = new CopyOnWriteArrayList<>(); + private final List multiCollectors = new CopyOnWriteArrayList<>(); - public void register(MultiCollector collector) { - for (String prometheusName : collector.getPrometheusNames()) { - if (!prometheusNames.add(prometheusName)) { - throw new IllegalStateException("Can't register " + prometheusName + " because that name is already registered."); - } - } - multiCollectors.add(collector); - } + public void register(Collector collector) { + String prometheusName = collector.getPrometheusName(); + if (prometheusName != null) { + if (!prometheusNames.add(prometheusName)) { + throw new IllegalStateException( + "Can't register " + + prometheusName + + " because a metric with that name is already registered."); + } + } + collectors.add(collector); + } - public void unregister(Collector collector) { - collectors.remove(collector); - String prometheusName = collector.getPrometheusName(); - if (prometheusName != null) { - prometheusNames.remove(collector.getPrometheusName()); - } - } + public void register(MultiCollector collector) { + for (String prometheusName : collector.getPrometheusNames()) { + if (!prometheusNames.add(prometheusName)) { + throw new IllegalStateException( + "Can't register " + prometheusName + " because that name is already registered."); + } + } + multiCollectors.add(collector); + } - public void unregister(MultiCollector collector) { - multiCollectors.remove(collector); - for (String prometheusName : collector.getPrometheusNames()) { - prometheusNames.remove(prometheusName(prometheusName)); - } - } + public void unregister(Collector collector) { + collectors.remove(collector); + String prometheusName = collector.getPrometheusName(); + if (prometheusName != null) { + prometheusNames.remove(collector.getPrometheusName()); + } + } - public MetricSnapshots scrape() { - return scrape((PrometheusScrapeRequest) null); - } + public void unregister(MultiCollector collector) { + multiCollectors.remove(collector); + for (String prometheusName : collector.getPrometheusNames()) { + prometheusNames.remove(prometheusName(prometheusName)); + } + } - public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - MetricSnapshot snapshot = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); - if (snapshot != null) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); - } - } - for (MultiCollector collector : multiCollectors) { - MetricSnapshots snaphots = scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); - for (MetricSnapshot snapshot : snaphots) { - if (result.containsMetricName(snapshot.getMetadata().getName())) { - throw new IllegalStateException(snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); - } - result.metricSnapshot(snapshot); - } - } - return result.build(); - } + public MetricSnapshots scrape() { + return scrape((PrometheusScrapeRequest) null); + } - public MetricSnapshots scrape(Predicate includedNames) { - if (includedNames == null) { - return scrape(); - } - return scrape(includedNames, null); - } + public MetricSnapshots scrape(PrometheusScrapeRequest scrapeRequest) { + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + MetricSnapshot snapshot = + scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); + if (snapshot != null) { + if (result.containsMetricName(snapshot.getMetadata().getName())) { + throw new IllegalStateException( + snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); + } + result.metricSnapshot(snapshot); + } + } + for (MultiCollector collector : multiCollectors) { + MetricSnapshots snapshots = + scrapeRequest == null ? collector.collect() : collector.collect(scrapeRequest); + for (MetricSnapshot snapshot : snapshots) { + if (result.containsMetricName(snapshot.getMetadata().getName())) { + throw new IllegalStateException( + snapshot.getMetadata().getPrometheusName() + ": duplicate metric name."); + } + result.metricSnapshot(snapshot); + } + } + return result.build(); + } - public MetricSnapshots scrape(Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { - if (includedNames == null) { - return scrape(scrapeRequest); - } - MetricSnapshots.Builder result = MetricSnapshots.builder(); - for (Collector collector : collectors) { - String prometheusName = collector.getPrometheusName(); - // prometheusName == null means the name is unknown, and we have to scrape to learn the name. - // prometheusName != null means we can skip the scrape if the name is excluded. - if (prometheusName == null || includedNames.test(prometheusName)) { - MetricSnapshot snapshot = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest); - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - for (MultiCollector collector : multiCollectors) { - List prometheusNames = collector.getPrometheusNames(); - // empty prometheusNames means the names are unknown, and we have to scrape to learn the names. - // non-empty prometheusNames means we can exclude the collector if all names are excluded by the filter. - boolean excluded = !prometheusNames.isEmpty(); - for (String prometheusName : prometheusNames) { - if (includedNames.test(prometheusName)) { - excluded = false; - break; - } - } - if (!excluded) { - MetricSnapshots snapshots = scrapeRequest == null ? collector.collect(includedNames) : collector.collect(includedNames, scrapeRequest); - for (MetricSnapshot snapshot : snapshots) { - if (snapshot != null) { - result.metricSnapshot(snapshot); - } - } - } - } - return result.build(); - } + public MetricSnapshots scrape(Predicate includedNames) { + if (includedNames == null) { + return scrape(); + } + return scrape(includedNames, null); + } + public MetricSnapshots scrape( + Predicate includedNames, PrometheusScrapeRequest scrapeRequest) { + if (includedNames == null) { + return scrape(scrapeRequest); + } + MetricSnapshots.Builder result = MetricSnapshots.builder(); + for (Collector collector : collectors) { + String prometheusName = collector.getPrometheusName(); + // prometheusName == null means the name is unknown, and we have to scrape to learn the name. + // prometheusName != null means we can skip the scrape if the name is excluded. + if (prometheusName == null || includedNames.test(prometheusName)) { + MetricSnapshot snapshot = + scrapeRequest == null + ? collector.collect(includedNames) + : collector.collect(includedNames, scrapeRequest); + if (snapshot != null) { + result.metricSnapshot(snapshot); + } + } + } + for (MultiCollector collector : multiCollectors) { + List prometheusNames = collector.getPrometheusNames(); + // empty prometheusNames means the names are unknown, and we have to scrape to learn the + // names. + // non-empty prometheusNames means we can exclude the collector if all names are excluded by + // the filter. + boolean excluded = !prometheusNames.isEmpty(); + for (String prometheusName : prometheusNames) { + if (includedNames.test(prometheusName)) { + excluded = false; + break; + } + } + if (!excluded) { + MetricSnapshots snapshots = + scrapeRequest == null + ? collector.collect(includedNames) + : collector.collect(includedNames, scrapeRequest); + for (MetricSnapshot snapshot : snapshots) { + if (snapshot != null) { + result.metricSnapshot(snapshot); + } + } + } + } + return result.build(); + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java index b5286b0c2..ca9109b96 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Exemplars.java @@ -9,141 +9,131 @@ /** * Immutable container for Exemplars. - *

- * This is currently backed by a {@code List}. May be refactored later to use a more efficient data structure. + * + *

This is currently backed by a {@code List}. May be refactored later to use a more + * efficient data structure. */ public class Exemplars implements Iterable { - /** - * EMPTY means no Exemplars. - */ - public static final Exemplars EMPTY = new Exemplars(Collections.emptyList()); - private final List exemplars; - - private Exemplars(Collection exemplars) { - ArrayList copy = new ArrayList<>(exemplars.size()); - for (Exemplar exemplar : exemplars) { - if (exemplar == null) { - throw new NullPointerException("Illegal null value in Exemplars"); - } - copy.add(exemplar); - } - this.exemplars = Collections.unmodifiableList(copy); - } + /** EMPTY means no Exemplars. */ + public static final Exemplars EMPTY = new Exemplars(Collections.emptyList()); - /** - * Create a new Exemplars instance. - * You can either create Exemplars with one of the static {@code Exemplars.of(...)} methods, - * or you can use the {@link Exemplars#builder()}. - * - * @param exemplars a copy of the exemplars collection will be created. - */ - public static Exemplars of(Collection exemplars) { - return new Exemplars(exemplars); - } - - /** - * Create a new Exemplars instance. - * You can either create Exemplars with one of the static {@code Exemplars.of(...)} methods, - * or you can use the {@link Exemplars#builder()}. - * - * @param exemplars a copy of the exemplars array will be created. - */ - public static Exemplars of(Exemplar... exemplars) { - return new Exemplars(Arrays.asList(exemplars)); - } - - @Override - public Iterator iterator() { - return exemplars.iterator(); - } - - public int size() { - return exemplars.size(); - } + private final List exemplars; - public Exemplar get(int index) { - return exemplars.get(index); + private Exemplars(Collection exemplars) { + List copy = new ArrayList<>(exemplars.size()); + for (Exemplar exemplar : exemplars) { + if (exemplar == null) { + throw new NullPointerException("Illegal null value in Exemplars"); + } + copy.add(exemplar); } - - /** - * This is used by classic histograms to find an exemplar with a value between lowerBound and upperBound. - * If there is more than one exemplar within the bounds the one with the newest time stamp is returned. - */ - public Exemplar get(double lowerBound, double upperBound) { - Exemplar result = null; - for (int i = 0; i < exemplars.size(); i++) { - Exemplar exemplar = exemplars.get(i); - double value = exemplar.getValue(); - if (value > lowerBound && value <= upperBound) { - if (result == null) { - result = exemplar; - } else if (result.hasTimestamp() && exemplar.hasTimestamp()) { - if (exemplar.getTimestampMillis() > result.getTimestampMillis()) { - result = exemplar; - } - } - } + this.exemplars = Collections.unmodifiableList(copy); + } + + /** + * Create a new Exemplars instance. You can either create Exemplars with one of the static {@code + * Exemplars.of(...)} methods, or you can use the {@link Exemplars#builder()}. + * + * @param exemplars a copy of the exemplars collection will be created. + */ + public static Exemplars of(Collection exemplars) { + return new Exemplars(exemplars); + } + + /** + * Create a new Exemplars instance. You can either create Exemplars with one of the static {@code + * Exemplars.of(...)} methods, or you can use the {@link Exemplars#builder()}. + * + * @param exemplars a copy of the exemplars array will be created. + */ + public static Exemplars of(Exemplar... exemplars) { + return new Exemplars(Arrays.asList(exemplars)); + } + + @Override + public Iterator iterator() { + return exemplars.iterator(); + } + + public int size() { + return exemplars.size(); + } + + public Exemplar get(int index) { + return exemplars.get(index); + } + + /** + * This is used by classic histograms to find an exemplar with a value between lowerBound and + * upperBound. If there is more than one exemplar within the bounds the one with the newest time + * stamp is returned. + */ + public Exemplar get(double lowerBound, double upperBound) { + Exemplar result = null; + for (Exemplar exemplar : exemplars) { + double value = exemplar.getValue(); + if (value > lowerBound && value <= upperBound) { + if (result == null) { + result = exemplar; + } else if (result.hasTimestamp() && exemplar.hasTimestamp()) { + if (exemplar.getTimestampMillis() > result.getTimestampMillis()) { + result = exemplar; + } } - return result; + } } - - /** - * Find the Exemplar with the newest timestamp. May return {@code null}. - */ - public Exemplar getLatest() { - Exemplar latest = null; - for (int i=0; i exemplars = new ArrayList<>(); + private final ArrayList exemplars = new ArrayList<>(); - private Builder() { - } + private Builder() {} - /** - * Add an exemplar. This can be called multiple times to add multiple exemplars. - */ - public Builder exemplar(Exemplar exemplar) { - exemplars.add(exemplar); - return this; - } + /** Add an exemplar. This can be called multiple times to add multiple exemplars. */ + public Builder exemplar(Exemplar exemplar) { + exemplars.add(exemplar); + return this; + } - /** - * Add all exemplars form the collection. - */ - public Builder exemplars(Collection exemplars) { - this.exemplars.addAll(exemplars); - return this; - } + /** Add all exemplars form the collection. */ + public Builder exemplars(Collection exemplars) { + this.exemplars.addAll(exemplars); + return this; + } - public Exemplars build() { - return Exemplars.of(exemplars); - } + public Exemplars build() { + return Exemplars.of(exemplars); } + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java index 67a0dcf1f..170ec4fc2 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Labels.java @@ -1,5 +1,8 @@ package io.prometheus.metrics.model.snapshots; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -7,434 +10,427 @@ import java.util.List; import java.util.stream.Stream; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.isValidLabelName; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; - -/** - * Immutable set of name/value pairs, sorted by name. - */ +/** Immutable set of name/value pairs, sorted by name. */ public class Labels implements Comparable, Iterable

- * Dots are treated as underscores, so {@code contains("my.label")} and {@code contains("my_label")} are the same. - */ - public boolean contains(String labelName) { - return get(labelName) != null; - } - - /** - * Get the label value for a given label name. - *

- * Returns {@code null} if the {@code labelName} is not found. - *

- * Dots are treated as underscores, so {@code get("my.label")} and {@code get("my_label")} are the same. - */ - public String get(String labelName) { - labelName = prometheusName(labelName); - for (int i=0; i 0 && prometheusNames[i - 1].equals(prometheusNames[i])) { - throw new IllegalArgumentException(names[i] + ": duplicate label name"); - } - } - } - - private static void sort(String[] names, String[] prometheusNames, String[] values) { - // bubblesort - int n = prometheusNames.length; - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - i - 1; j++) { - if (prometheusNames[j].compareTo(prometheusNames[j + 1]) > 0) { - swap(j, j + 1, names, prometheusNames, values); - } - } - } - } - - private static void swap(int i, int j, String[] names, String[] prometheusNames, String[] values) { - String tmp = names[j]; - names[j] = names[i]; - names[i] = tmp; - tmp = values[j]; - values[j] = values[i]; - values[i] = tmp; - if (prometheusNames != names) { - tmp = prometheusNames[j]; - prometheusNames[j] = prometheusNames[i]; - prometheusNames[i] = tmp; - } - } - - @Override - public Iterator

- * This is used by Prometheus exposition formats. - */ - public String getPrometheusName(int i) { - return prometheusNames[i]; - } - - public String getValue(int i) { + prometheusNames[i] = PrometheusNaming.prometheusName(names[i]); + } + } + return prometheusNames; + } + + /** + * Create a new Labels instance. You can either create Labels with one of the static {@code + * Labels.of(...)} methods, or you can use the {@link Labels#builder()}. + * + * @param names label names. {@link PrometheusNaming#isValidLabelName(String)} must be true for + * each name. Use {@link PrometheusNaming#sanitizeLabelName(String)} to convert arbitrary + * strings to valid label names. Label names must be unique (no duplicate label names). + * @param values label values. {@code names.size()} must be equal to {@code values.size()}. + */ + public static Labels of(List names, List values) { + if (names.size() != values.size()) { + throw new IllegalArgumentException("Names and values must have the same size."); + } + if (names.isEmpty()) { + return EMPTY; + } + String[] namesCopy = names.toArray(new String[0]); + String[] valuesCopy = values.toArray(new String[0]); + String[] prometheusNames = makePrometheusNames(namesCopy); + sortAndValidate(namesCopy, prometheusNames, valuesCopy); + return new Labels(namesCopy, prometheusNames, valuesCopy); + } + + /** + * Create a new Labels instance. You can either create Labels with one of the static {@code + * Labels.of(...)} methods, or you can use the {@link Labels#builder()}. + * + * @param names label names. {@link PrometheusNaming#isValidLabelName(String)} must be true for + * each name. Use {@link PrometheusNaming#sanitizeLabelName(String)} to convert arbitrary + * strings to valid label names. Label names must be unique (no duplicate label names). + * @param values label values. {@code names.length} must be equal to {@code values.length}. + */ + public static Labels of(String[] names, String[] values) { + if (names.length != values.length) { + throw new IllegalArgumentException("Names and values must have the same length."); + } + if (names.length == 0) { + return EMPTY; + } + String[] namesCopy = Arrays.copyOf(names, names.length); + String[] valuesCopy = Arrays.copyOf(values, values.length); + String[] prometheusNames = makePrometheusNames(namesCopy); + sortAndValidate(namesCopy, prometheusNames, valuesCopy); + return new Labels(namesCopy, prometheusNames, valuesCopy); + } + + /** + * Test if these labels contain a specific label name. + * + *

Dots are treated as underscores, so {@code contains("my.label")} and {@code + * contains("my_label")} are the same. + */ + public boolean contains(String labelName) { + return get(labelName) != null; + } + + /** + * Get the label value for a given label name. + * + *

Returns {@code null} if the {@code labelName} is not found. + * + *

Dots are treated as underscores, so {@code get("my.label")} and {@code get("my_label")} are + * the same. + */ + public String get(String labelName) { + labelName = prometheusName(labelName); + for (int i = 0; i < prometheusNames.length; i++) { + if (prometheusNames[i].equals(labelName)) { return values[i]; - } - - /** - * Create a new Labels instance containing the labels of this and the labels of other. - * This and other must not contain the same label name. - */ - public Labels merge(Labels other) { - if (this.isEmpty()) { - return other; - } - if (other.isEmpty()) { - return this; - } - String[] names = new String[this.names.length + other.names.length]; - String[] prometheusNames = names; - if (this.names != this.prometheusNames || other.names != other.prometheusNames) { - prometheusNames = new String[names.length]; - } - String[] values = new String[names.length]; - int thisPos = 0; - int otherPos = 0; - while (thisPos + otherPos < names.length) { - if (thisPos >= this.names.length) { - names[thisPos + otherPos] = other.names[otherPos]; - values[thisPos + otherPos] = other.values[otherPos]; - if (prometheusNames != names) { - prometheusNames[thisPos + otherPos] = other.prometheusNames[otherPos]; - } - otherPos++; - } else if (otherPos >= other.names.length) { - names[thisPos + otherPos] = this.names[thisPos]; - values[thisPos + otherPos] = this.values[thisPos]; - if (prometheusNames != names) { - prometheusNames[thisPos + otherPos] = this.prometheusNames[thisPos]; - } - thisPos++; - } else if (this.prometheusNames[thisPos].compareTo(other.prometheusNames[otherPos]) < 0) { - names[thisPos + otherPos] = this.names[thisPos]; - values[thisPos + otherPos] = this.values[thisPos]; - if (prometheusNames != names) { - prometheusNames[thisPos + otherPos] = this.prometheusNames[thisPos]; - } - thisPos++; - } else if (this.prometheusNames[thisPos].compareTo(other.prometheusNames[otherPos]) > 0) { - names[thisPos + otherPos] = other.names[otherPos]; - values[thisPos + otherPos] = other.values[otherPos]; - if (prometheusNames != names) { - prometheusNames[thisPos + otherPos] = other.prometheusNames[otherPos]; - } - otherPos++; - } else { - throw new IllegalArgumentException("Duplicate label name: '" + this.names[thisPos] + "'."); - } - } - return new Labels(names, prometheusNames, values); - } - - /** - * Create a new Labels instance containing the labels of this and the label passed as name and value. - * The label name must not already be contained in this Labels instance. - */ - public Labels add(String name, String value) { - return merge(Labels.of(name, value)); - } - - /** - * Create a new Labels instance containing the labels of this and the labels passed as names and values. - * The new label names must not already be contained in this Labels instance. - */ - public Labels merge(String[] names, String[] values) { - if (this.equals(EMPTY)) { - return Labels.of(names, values); - } - String[] mergedNames = new String[this.names.length + names.length]; - String[] mergedValues = new String[this.values.length + values.length]; - System.arraycopy(this.names, 0, mergedNames, 0, this.names.length); - System.arraycopy(this.values, 0, mergedValues, 0, this.values.length); - System.arraycopy(names, 0, mergedNames, this.names.length, names.length); - System.arraycopy(values, 0, mergedValues, this.values.length, values.length); - String[] prometheusNames = makePrometheusNames(mergedNames); - sortAndValidate(mergedNames, prometheusNames, mergedValues); - return new Labels(mergedNames, prometheusNames, mergedValues); - } - - public boolean hasSameNames(Labels other) { - return Arrays.equals(prometheusNames, other.prometheusNames); - } - - public boolean hasSameValues(Labels other) { - return Arrays.equals(values, other.values); - } - - @Override - public int compareTo(Labels other) { - int result = compare(prometheusNames, other.prometheusNames); - if (result != 0) { - return result; - } - return compare(values, other.values); - } - - // Looks like Java doesn't have a compareTo() method for arrays. - private int compare(String[] array1, String[] array2) { - int result; - for (int i = 0; i < array1.length; i++) { - if (array2.length <= i) { - return 1; - } - result = array1[i].compareTo(array2[i]); - if (result != 0) { - return result; - } + } + } + return null; + } + + private static void sortAndValidate(String[] names, String[] prometheusNames, String[] values) { + sort(names, prometheusNames, values); + validateNames(names, prometheusNames); + } + + private static void validateNames(String[] names, String[] prometheusNames) { + for (int i = 0; i < names.length; i++) { + if (!isValidLabelName(names[i])) { + throw new IllegalArgumentException("'" + names[i] + "' is an illegal label name"); + } + // The arrays are sorted, so duplicates are next to each other + if (i > 0 && prometheusNames[i - 1].equals(prometheusNames[i])) { + throw new IllegalArgumentException(names[i] + ": duplicate label name"); + } + } + } + + private static void sort(String[] names, String[] prometheusNames, String[] values) { + // bubblesort + int n = prometheusNames.length; + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < n - i - 1; j++) { + if (prometheusNames[j].compareTo(prometheusNames[j + 1]) > 0) { + swap(j, j + 1, names, prometheusNames, values); } - if (array2.length > array1.length) { - return -1; + } + } + } + + private static void swap( + int i, int j, String[] names, String[] prometheusNames, String[] values) { + String tmp = names[j]; + names[j] = names[i]; + names[i] = tmp; + tmp = values[j]; + values[j] = values[i]; + values[i] = tmp; + if (prometheusNames != names) { + tmp = prometheusNames[j]; + prometheusNames[j] = prometheusNames[i]; + prometheusNames[i] = tmp; + } + } + + @Override + public Iterator

This is used by Prometheus exposition formats. + */ + public String getPrometheusName(int i) { + return prometheusNames[i]; + } + + public String getValue(int i) { + return values[i]; + } + + /** + * Create a new Labels instance containing the labels of this and the labels of other. This and + * other must not contain the same label name. + */ + public Labels merge(Labels other) { + if (this.isEmpty()) { + return other; + } + if (other.isEmpty()) { + return this; + } + String[] names = new String[this.names.length + other.names.length]; + String[] prometheusNames = names; + if (this.names != this.prometheusNames || other.names != other.prometheusNames) { + prometheusNames = new String[names.length]; + } + String[] values = new String[names.length]; + int thisPos = 0; + int otherPos = 0; + while (thisPos + otherPos < names.length) { + if (thisPos >= this.names.length) { + names[thisPos + otherPos] = other.names[otherPos]; + values[thisPos + otherPos] = other.values[otherPos]; + if (prometheusNames != names) { + prometheusNames[thisPos + otherPos] = other.prometheusNames[otherPos]; } - return 0; - } - - private List

- * However, for debugging it's better to show the original names rather than the Prometheus names. - */ - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - b.append("{"); - for (int i = 0; i < names.length; i++) { - if (i > 0) { - b.append(","); - } - b.append(names[i]); - b.append("=\""); - appendEscapedLabelValue(b, values[i]); - b.append("\""); + thisPos++; + } else if (this.prometheusNames[thisPos].compareTo(other.prometheusNames[otherPos]) < 0) { + names[thisPos + otherPos] = this.names[thisPos]; + values[thisPos + otherPos] = this.values[thisPos]; + if (prometheusNames != names) { + prometheusNames[thisPos + otherPos] = this.prometheusNames[thisPos]; } - b.append("}"); - return b.toString(); - } - - private void appendEscapedLabelValue(StringBuilder b, String value) { - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - switch (c) { - case '\\': - b.append("\\\\"); - break; - case '\"': - b.append("\\\""); - break; - case '\n': - b.append("\\n"); - break; - default: - b.append(c); - } + thisPos++; + } else if (this.prometheusNames[thisPos].compareTo(other.prometheusNames[otherPos]) > 0) { + names[thisPos + otherPos] = other.names[otherPos]; + values[thisPos + otherPos] = other.values[otherPos]; + if (prometheusNames != names) { + prometheusNames[thisPos + otherPos] = other.prometheusNames[otherPos]; } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Labels labels = (Labels) o; - return labels.hasSameNames(this) && labels.hasSameValues(this); - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(prometheusNames); - result = 31 * result + Arrays.hashCode(values); + otherPos++; + } else { + throw new IllegalArgumentException("Duplicate label name: '" + this.names[thisPos] + "'."); + } + } + return new Labels(names, prometheusNames, values); + } + + /** + * Create a new Labels instance containing the labels of this and the label passed as name and + * value. The label name must not already be contained in this Labels instance. + */ + public Labels add(String name, String value) { + return merge(Labels.of(name, value)); + } + + /** + * Create a new Labels instance containing the labels of this and the labels passed as names and + * values. The new label names must not already be contained in this Labels instance. + */ + public Labels merge(String[] names, String[] values) { + if (this.equals(EMPTY)) { + return Labels.of(names, values); + } + String[] mergedNames = new String[this.names.length + names.length]; + String[] mergedValues = new String[this.values.length + values.length]; + System.arraycopy(this.names, 0, mergedNames, 0, this.names.length); + System.arraycopy(this.values, 0, mergedValues, 0, this.values.length); + System.arraycopy(names, 0, mergedNames, this.names.length, names.length); + System.arraycopy(values, 0, mergedValues, this.values.length, values.length); + String[] prometheusNames = makePrometheusNames(mergedNames); + sortAndValidate(mergedNames, prometheusNames, mergedValues); + return new Labels(mergedNames, prometheusNames, mergedValues); + } + + public boolean hasSameNames(Labels other) { + return Arrays.equals(prometheusNames, other.prometheusNames); + } + + public boolean hasSameValues(Labels other) { + return Arrays.equals(values, other.values); + } + + @Override + public int compareTo(Labels other) { + int result = compare(prometheusNames, other.prometheusNames); + if (result != 0) { + return result; + } + return compare(values, other.values); + } + + // Looks like Java doesn't have a compareTo() method for arrays. + private int compare(String[] array1, String[] array2) { + int result; + for (int i = 0; i < array1.length; i++) { + if (array2.length <= i) { + return 1; + } + result = array1[i].compareTo(array2[i]); + if (result != 0) { return result; - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - private final List names = new ArrayList<>(); - private final List values = new ArrayList<>(); - - private Builder() { - } - - /** - * Add a label. Call multiple times to add multiple labels. - */ - public Builder label(String name, String value) { - names.add(name); - values.add(value); - return this; - } - - public Labels build() { - return Labels.of(names, values); - } - } + } + } + if (array2.length > array1.length) { + return -1; + } + return 0; + } + + private List

However, for debugging it's better to show the original names rather than the Prometheus + * names. + */ + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + b.append("{"); + for (int i = 0; i < names.length; i++) { + if (i > 0) { + b.append(","); + } + b.append(names[i]); + b.append("=\""); + appendEscapedLabelValue(b, values[i]); + b.append("\""); + } + b.append("}"); + return b.toString(); + } + + private void appendEscapedLabelValue(StringBuilder b, String value) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + switch (c) { + case '\\': + b.append("\\\\"); + break; + case '\"': + b.append("\\\""); + break; + case '\n': + b.append("\\n"); + break; + default: + b.append(c); + } + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Labels labels = (Labels) o; + return labels.hasSameNames(this) && labels.hasSameValues(this); + } + + @Override + public int hashCode() { + int result = Arrays.hashCode(prometheusNames); + result = 31 * result + Arrays.hashCode(values); + return result; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final List names = new ArrayList<>(); + private final List values = new ArrayList<>(); + + private Builder() {} + + /** Add a label. Call multiple times to add multiple labels. */ + public Builder label(String name, String value) { + names.add(name); + values.add(value); + return this; + } + + public Labels build() { + return Labels.of(names, values); + } + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantile.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantile.java index 7d5c1c166..1601920f0 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantile.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantile.java @@ -1,34 +1,34 @@ package io.prometheus.metrics.model.snapshots; -/** - * Immutable representation of a Quantile. - */ +/** Immutable representation of a Quantile. */ public class Quantile { - private final double quantile; - private final double value; + private final double quantile; + private final double value; - /** - * @param quantile expecting 0.0 <= quantile <= 1.0, otherwise an {@link IllegalArgumentException} will be thrown. - * @param value - */ - public Quantile(double quantile, double value) { - this.quantile = quantile; - this.value = value; - validate(); - } + /** + * @param quantile expecting 0.0 <= quantile <= 1.0, otherwise an {@link + * IllegalArgumentException} will be thrown. + * @param value the quantile value + */ + public Quantile(double quantile, double value) { + this.quantile = quantile; + this.value = value; + validate(); + } - public double getQuantile() { - return quantile; - } + public double getQuantile() { + return quantile; + } - public double getValue() { - return value; - } + public double getValue() { + return value; + } - private void validate() { - if (quantile < 0.0 || quantile > 1.0) { - throw new IllegalArgumentException(quantile + ": Illegal quantile. Expecting 0 <= quantile <= 1"); - } + private void validate() { + if (quantile < 0.0 || quantile > 1.0) { + throw new IllegalArgumentException( + quantile + ": Illegal quantile. Expecting 0 <= quantile <= 1"); } + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantiles.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantiles.java index e4a7b3621..135af6658 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantiles.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/Quantiles.java @@ -2,91 +2,86 @@ import java.util.*; -/** - * Immutable list of quantiles. - */ +/** Immutable list of quantiles. */ public class Quantiles implements Iterable { - private final List quantiles; - public static final Quantiles EMPTY = new Quantiles(Collections.emptyList()); - - private Quantiles(List quantiles) { - quantiles = new ArrayList<>(quantiles); - quantiles.sort(Comparator.comparing(Quantile::getQuantile)); - this.quantiles = Collections.unmodifiableList(quantiles); - validate(); + private final List quantiles; + public static final Quantiles EMPTY = new Quantiles(Collections.emptyList()); + + private Quantiles(List quantiles) { + quantiles = new ArrayList<>(quantiles); + quantiles.sort(Comparator.comparing(Quantile::getQuantile)); + this.quantiles = Collections.unmodifiableList(quantiles); + validate(); + } + + private void validate() { + for (int i = 0; i < quantiles.size() - 1; i++) { + if (quantiles.get(i).getQuantile() == quantiles.get(i + 1).getQuantile()) { + throw new IllegalArgumentException( + "Duplicate " + quantiles.get(i).getQuantile() + " quantile."); + } } - - private void validate() { - for (int i=0; i< quantiles.size() - 1; i++) { - if (quantiles.get(i).getQuantile() == quantiles.get(i+1).getQuantile()) { - throw new IllegalArgumentException("Duplicate " + quantiles.get(i).getQuantile() + " quantile."); - } - } - } - - /** - * Create a new Quantiles instance. - * You can either create Quantiles with one of the static {@code Quantiles.of(...)} methods, - * or you can use the {@link Quantiles#builder()}. - */ - public static Quantiles of(List quantiles) { - return new Quantiles(quantiles); + } + + /** + * Create a new Quantiles instance. You can either create Quantiles with one of the static {@code + * Quantiles.of(...)} methods, or you can use the {@link Quantiles#builder()}. + */ + public static Quantiles of(List quantiles) { + return new Quantiles(quantiles); + } + + /** + * Create a new Quantiles instance. You can either create Quantiles with one of the static {@code + * Quantiles.of(...)} methods, or you can use the {@link Quantiles#builder()}. + */ + public static Quantiles of(Quantile... quantiles) { + return of(Arrays.asList(quantiles)); + } + + public int size() { + return quantiles.size(); + } + + public Quantile get(int i) { + return quantiles.get(i); + } + + @Override + public Iterator iterator() { + return quantiles.iterator(); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final List quantiles = new ArrayList<>(); + + private Builder() {} + + /** Add a quantile. Call multiple times to add multiple quantiles. */ + public Builder quantile(Quantile quantile) { + quantiles.add(quantile); + return this; } /** - * Create a new Quantiles instance. - * You can either create Quantiles with one of the static {@code Quantiles.of(...)} methods, - * or you can use the {@link Quantiles#builder()}. + * Add a quantile. Call multiple times to add multiple quantiles. + * + * @param quantile 0.0 <= quantile <= 1.0 + * @param value the quantile value */ - public static Quantiles of(Quantile... quantiles) { - return of(Arrays.asList(quantiles)); - } - - public int size() { - return quantiles.size(); + public Builder quantile(double quantile, double value) { + quantiles.add(new Quantile(quantile, value)); + return this; } - public Quantile get(int i) { - return quantiles.get(i); + public Quantiles build() { + return new Quantiles(quantiles); } - - @Override - public Iterator iterator() { - return quantiles.iterator(); - } - - public static Builder builder() { - return new Builder(); - } - - public static class Builder { - - private final List quantiles = new ArrayList<>(); - - private Builder() { - } - - /** - * Add a quantile. Call multiple times to add multiple quantiles. - */ - public Builder quantile(Quantile quantile) { - quantiles.add(quantile); - return this; - } - - /** - * Add a quantile. Call multiple times to add multiple quantiles. - * @param quantile 0.0 <= quantile <= 1.0 - */ - public Builder quantile(double quantile, double value) { - quantiles.add(new Quantile(quantile, value)); - return this; - } - - public Quantiles build() { - return new Quantiles(quantiles); - } - } - + } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java index ddcc94bc2..591732137 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/StateSetSnapshot.java @@ -8,225 +8,221 @@ import java.util.List; import java.util.stream.Stream; -/** - * Immutable snapshot of a StateSet metric. - */ +/** Immutable snapshot of a StateSet metric. */ public final class StateSetSnapshot extends MetricSnapshot { + /** + * To create a new {@link StateSetSnapshot}, you can either call the constructor directly or use + * the builder with {@link StateSetSnapshot#builder()}. + * + * @param metadata See {@link MetricMetadata} for more naming conventions. + * @param data the constructor will create a sorted copy of the collection. + */ + public StateSetSnapshot(MetricMetadata metadata, Collection data) { + super(metadata, data); + validate(); + } + + private void validate() { + if (getMetadata().hasUnit()) { + throw new IllegalArgumentException("An state set metric cannot have a unit."); + } + for (StateSetDataPointSnapshot entry : getDataPoints()) { + if (entry.getLabels().contains(getMetadata().getPrometheusName())) { + throw new IllegalArgumentException( + "Label name " + getMetadata().getPrometheusName() + " is reserved."); + } + } + } + + @Override + public List getDataPoints() { + return (List) dataPoints; + } + + public static class StateSetDataPointSnapshot extends DataPointSnapshot + implements Iterable { + private final String[] names; + private final boolean[] values; + /** - * To create a new {@link StateSetSnapshot}, you can either call the constructor directly or use - * the builder with {@link StateSetSnapshot#builder()}. + * To create a new {@link StateSetDataPointSnapshot}, you can either call the constructor + * directly or use the Builder with {@link StateSetDataPointSnapshot#builder()}. * - * @param metadata See {@link MetricMetadata} for more naming conventions. - * @param data the constructor will create a sorted copy of the collection. + * @param names state names. Must have at least 1 entry. The constructor will create a copy of + * the array. + * @param values state values. Must have the same length as {@code names}. The constructor will + * create a copy of the array. + * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels. + */ + public StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels) { + this(names, values, labels, 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 StateSetSnapshot(MetricMetadata metadata, Collection data) { - super(metadata, data); - validate(); + public StateSetDataPointSnapshot( + String[] names, boolean[] values, Labels labels, long scrapeTimestampMillis) { + super(labels, 0L, scrapeTimestampMillis); + if (names.length == 0) { + throw new IllegalArgumentException("StateSet must have at least one state."); + } + if (names.length != values.length) { + throw new IllegalArgumentException("names[] and values[] must have the same length"); + } + String[] namesCopy = Arrays.copyOf(names, names.length); + boolean[] valuesCopy = Arrays.copyOf(values, names.length); + sort(namesCopy, valuesCopy); + this.names = namesCopy; + this.values = valuesCopy; + validate(); + } + + public int size() { + return names.length; + } + + public String getName(int i) { + return names[i]; + } + + public boolean isTrue(int i) { + return values[i]; } private void validate() { - if (getMetadata().hasUnit()) { - throw new IllegalArgumentException("An state set metric cannot have a unit."); + for (int i = 0; i < names.length; i++) { + if (names[i].isEmpty()) { + throw new IllegalArgumentException("Empty string as state name"); } - for (StateSetDataPointSnapshot entry : getDataPoints()) { - if (entry.getLabels().contains(getMetadata().getPrometheusName())) { - throw new IllegalArgumentException("Label name " + getMetadata().getPrometheusName() + " is reserved."); - } + if (i > 0 && names[i - 1].equals(names[i])) { + throw new IllegalArgumentException(names[i] + " duplicate state name"); } + } } - @Override - public List getDataPoints() { - return (List) dataPoints; - } - - - public static class StateSetDataPointSnapshot extends DataPointSnapshot implements Iterable { - private final String[] names; - private final boolean[] values; - - /** - * To create a new {@link StateSetDataPointSnapshot}, you can either call the constructor directly or use the - * Builder with {@link StateSetDataPointSnapshot#builder()}. - * - * @param names state names. Must have at least 1 entry. - * The constructor will create a copy of the array. - * @param values state values. Must have the same length as {@code names}. - * The constructor will create a copy of the array. - * @param labels must not be null. Use {@link Labels#EMPTY} if there are no labels. - */ - public StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels) { - this(names, values, labels, 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 StateSetDataPointSnapshot(String[] names, boolean[] values, Labels labels, long scrapeTimestampMillis) { - super(labels, 0L, scrapeTimestampMillis); - if (names.length == 0) { - throw new IllegalArgumentException("StateSet must have at least one state."); - } - if (names.length != values.length) { - throw new IllegalArgumentException("names[] and values[] must have the same length"); - } - String[] namesCopy = Arrays.copyOf(names, names.length); - boolean[] valuesCopy = Arrays.copyOf(values, names.length); - sort(namesCopy, valuesCopy); - this.names = namesCopy; - this.values = valuesCopy; - validate(); - } + private List asList() { + List result = new ArrayList<>(size()); + for (int i = 0; i < names.length; i++) { + result.add(new State(names[i], values[i])); + } + return Collections.unmodifiableList(result); + } - public int size() { - return names.length; - } + @Override + public Iterator iterator() { + return asList().iterator(); + } - public String getName(int i) { - return names[i]; - } + public Stream stream() { + return asList().stream(); + } - public boolean isTrue(int i) { - return values[i]; + private static void sort(String[] names, boolean[] values) { + // Bubblesort + int n = names.length; + for (int i = 0; i < n - 1; i++) { + for (int j = 0; j < n - i - 1; j++) { + if (names[j].compareTo(names[j + 1]) > 0) { + swap(j, j + 1, names, values); + } } + } + } - private void validate() { - for (int i = 0; i < names.length; i++) { - if (names[i].length() == 0) { - throw new IllegalArgumentException("Empty string as state name"); - } - if (i > 0 && names[i - 1].equals(names[i])) { - throw new IllegalArgumentException(names[i] + " duplicate state name"); - } - } - } + private static void swap(int i, int j, String[] names, boolean[] values) { + String tmpName = names[j]; + names[j] = names[i]; + names[i] = tmpName; + boolean tmpValue = values[j]; + values[j] = values[i]; + values[i] = tmpValue; + } - private List asList() { - List result = new ArrayList<>(size()); - for (int i = 0; i < names.length; i++) { - result.add(new State(names[i], values[i])); - } - return Collections.unmodifiableList(result); - } + public static Builder builder() { + return new Builder(); + } - @Override - public Iterator iterator() { - return asList().iterator(); - } + public static class Builder extends DataPointSnapshot.Builder { - public Stream stream() { - return asList().stream(); - } + private final List names = new ArrayList<>(); + private final List values = new ArrayList<>(); - private static void sort(String[] names, boolean[] values) { - // Bubblesort - int n = names.length; - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - i - 1; j++) { - if (names[j].compareTo(names[j + 1]) > 0) { - swap(j, j + 1, names, values); - } - } - } - } + private Builder() {} - private static void swap(int i, int j, String[] names, boolean[] values) { - String tmpName = names[j]; - names[j] = names[i]; - names[i] = tmpName; - boolean tmpValue = values[j]; - values[j] = values[i]; - values[i] = tmpValue; - } + /** Add a state. Call multiple times to add multiple states. */ + public Builder state(String name, boolean value) { + names.add(name); + values.add(value); + return this; + } - public static Builder builder() { - return new Builder(); - } + @Override + protected Builder self() { + return this; + } - public static class Builder extends DataPointSnapshot.Builder { - - private final ArrayList names = new ArrayList<>(); - private final ArrayList values = new ArrayList<>(); - - private Builder() {} - - /** - * Add a state. Call multple times to add multiple states. - */ - public Builder state(String name, boolean value) { - names.add(name); - values.add(value); - return this; - } - - @Override - protected Builder self() { - return this; - } - - public StateSetDataPointSnapshot build() { - boolean[] valuesArray = new boolean[values.size()]; - for (int i = 0; i < values.size(); i++) { - valuesArray[i] = values.get(i); - } - return new StateSetDataPointSnapshot(names.toArray(new String[]{}), valuesArray, labels, scrapeTimestampMillis); - } + public StateSetDataPointSnapshot build() { + boolean[] valuesArray = new boolean[values.size()]; + for (int i = 0; i < values.size(); i++) { + valuesArray[i] = values.get(i); } + return new StateSetDataPointSnapshot( + names.toArray(new String[] {}), valuesArray, labels, scrapeTimestampMillis); + } } + } - public static class State { - private final String name; - private final boolean value; - - private State(String name, boolean value) { - this.name = name; - this.value = value; - } + public static class State { + private final String name; + private final boolean value; - public String getName() { - return name; - } + private State(String name, boolean value) { + this.name = name; + this.value = value; + } - public boolean isTrue() { - return value; - } + public String getName() { + return name; } - public static Builder builder() { - return new Builder(); + public boolean isTrue() { + return value; } + } - public static class Builder extends MetricSnapshot.Builder { + public static Builder builder() { + return new Builder(); + } - private final List dataPoints = new ArrayList<>(); + public static class Builder extends MetricSnapshot.Builder { - private Builder() { - } + private final List dataPoints = new ArrayList<>(); - /** - * Add a data point. Call multiple times to add multiple data points. - */ - public Builder dataPoint(StateSetDataPointSnapshot dataPoint) { - dataPoints.add(dataPoint); - return this; - } + private Builder() {} - @Override - public Builder unit(Unit unit) { - throw new IllegalArgumentException("StateSet metric cannot have a unit."); - } + /** Add a data point. Call multiple times to add multiple data points. */ + public Builder dataPoint(StateSetDataPointSnapshot dataPoint) { + dataPoints.add(dataPoint); + return this; + } - @Override - public StateSetSnapshot build() { - return new StateSetSnapshot(buildMetadata(), dataPoints); - } + @Override + public Builder unit(Unit unit) { + throw new IllegalArgumentException("StateSet metric cannot have a unit."); + } - @Override - protected Builder self() { - return this; - } + @Override + public StateSetSnapshot build() { + return new StateSetSnapshot(buildMetadata(), dataPoints); + } + + @Override + protected Builder self() { + return this; } + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java index 91bcf74fc..6c7917bda 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MetricNameFilterTest.java @@ -2,79 +2,70 @@ import io.prometheus.metrics.model.snapshots.CounterSnapshot; import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot; -import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; import io.prometheus.metrics.model.snapshots.Labels; import io.prometheus.metrics.model.snapshots.MetricSnapshots; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Predicate; - public class MetricNameFilterTest { - private PrometheusRegistry registry; + private PrometheusRegistry registry; - @Before - public void setUp() { - registry = new PrometheusRegistry(); - } + @Before + public void setUp() { + registry = new PrometheusRegistry(); + } - @Test - public void testCounter() { - registry.register(() -> CounterSnapshot.builder() + @Test + public void testCounter() { + registry.register( + () -> + CounterSnapshot.builder() .name("counter1") .help("test counter 1") - .dataPoint(CounterDataPointSnapshot.builder() + .dataPoint( + CounterDataPointSnapshot.builder() .labels(Labels.of("path", "/hello")) .value(1.0) - .build() - ) - .dataPoint(CounterDataPointSnapshot.builder() + .build()) + .dataPoint( + CounterDataPointSnapshot.builder() .labels(Labels.of("path", "/goodbye")) .value(2.0) - .build() - ) - .build() - ); - registry.register(() -> CounterSnapshot.builder() + .build()) + .build()); + registry.register( + () -> + CounterSnapshot.builder() .name("counter2") .help("test counter 2") - .dataPoint(CounterDataPointSnapshot.builder() - .value(1.0) - .build() - ) - .build() - ); + .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build()) + .build()); - MetricNameFilter filter = MetricNameFilter.builder().build(); - Assert.assertEquals(2, registry.scrape(filter).size()); + MetricNameFilter filter = MetricNameFilter.builder().build(); + Assert.assertEquals(2, registry.scrape(filter).size()); - filter = MetricNameFilter.builder().nameMustStartWith("counter1").build(); - MetricSnapshots snapshots = registry.scrape(filter); - Assert.assertEquals(1, snapshots.size()); - Assert.assertEquals("counter1", snapshots.get(0).getMetadata().getName()); + filter = MetricNameFilter.builder().nameMustStartWith("counter1").build(); + MetricSnapshots snapshots = registry.scrape(filter); + Assert.assertEquals(1, snapshots.size()); + Assert.assertEquals("counter1", snapshots.get(0).getMetadata().getName()); - filter = MetricNameFilter.builder().nameMustNotStartWith("counter1").build(); - snapshots = registry.scrape(filter); - Assert.assertEquals(1, snapshots.size()); - Assert.assertEquals("counter2", snapshots.get(0).getMetadata().getName()); + filter = MetricNameFilter.builder().nameMustNotStartWith("counter1").build(); + snapshots = registry.scrape(filter); + Assert.assertEquals(1, snapshots.size()); + Assert.assertEquals("counter2", snapshots.get(0).getMetadata().getName()); - filter = MetricNameFilter.builder() - .nameMustBeEqualTo("counter2_total", "counter1_total") - .build(); - snapshots = registry.scrape(filter); - Assert.assertEquals(2, snapshots.size()); + filter = + MetricNameFilter.builder().nameMustBeEqualTo("counter2_total", "counter1_total").build(); + snapshots = registry.scrape(filter); + Assert.assertEquals(2, snapshots.size()); - filter = MetricNameFilter.builder() - .nameMustBeEqualTo("counter1_total") - .nameMustNotBeEqualTo("counter1_total") - .build(); - Assert.assertEquals(0, registry.scrape(filter).size()); - } + filter = + MetricNameFilter.builder() + .nameMustBeEqualTo("counter1_total") + .nameMustNotBeEqualTo("counter1_total") + .build(); + Assert.assertEquals(0, registry.scrape(filter).size()); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java index b36d58aa6..b302f4508 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/MultiCollectorNameFilterTest.java @@ -5,96 +5,96 @@ import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Predicate; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; public class MultiCollectorNameFilterTest { - private PrometheusRegistry registry; - private boolean[] collectCalled = {false}; - private Predicate includedNames = null; - List prometheusNames = new ArrayList<>(); - - @Before - public void setUp() { - registry = new PrometheusRegistry(); - collectCalled[0] = false; - includedNames = null; - prometheusNames = Collections.emptyList(); - - registry.register(new MultiCollector() { - @Override - public MetricSnapshots collect() { - collectCalled[0] = true; - return MetricSnapshots.builder() - .metricSnapshot(CounterSnapshot.builder() - .name("counter_1") - .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build()) - .build() - ) - .metricSnapshot(GaugeSnapshot.builder() - .name("gauge_2") - .dataPoint(GaugeDataPointSnapshot.builder().value(1.0).build()) - .build() - ) - .build(); - } - - @Override - public List getPrometheusNames() { - return prometheusNames; - } + private final boolean[] collectCalled = {false}; + private PrometheusRegistry registry; + private Predicate includedNames = null; + private List prometheusNames = new ArrayList<>(); + + @Before + public void setUp() { + registry = new PrometheusRegistry(); + collectCalled[0] = false; + includedNames = null; + prometheusNames = Collections.emptyList(); + + registry.register( + new MultiCollector() { + @Override + public MetricSnapshots collect() { + collectCalled[0] = true; + return MetricSnapshots.builder() + .metricSnapshot( + CounterSnapshot.builder() + .name("counter_1") + .dataPoint(CounterDataPointSnapshot.builder().value(1.0).build()) + .build()) + .metricSnapshot( + GaugeSnapshot.builder() + .name("gauge_2") + .dataPoint(GaugeDataPointSnapshot.builder().value(1.0).build()) + .build()) + .build(); + } + + @Override + public List getPrometheusNames() { + return prometheusNames; + } }); - } + } - @Test - public void testPartialFilter() { + @Test + public void testPartialFilter() { - includedNames = name -> name.equals("counter_1"); + includedNames = name -> name.equals("counter_1"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertTrue(collectCalled[0]); - Assert.assertEquals(1, snapshots.size()); - Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName()); - } + MetricSnapshots snapshots = registry.scrape(includedNames); + Assert.assertTrue(collectCalled[0]); + Assert.assertEquals(1, snapshots.size()); + Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName()); + } - @Test - public void testPartialFilterWithPrometheusNames() { + @Test + public void testPartialFilterWithPrometheusNames() { - includedNames = name -> name.equals("counter_1"); - prometheusNames = Arrays.asList("counter_1", "gauge_2"); + includedNames = name -> name.equals("counter_1"); + prometheusNames = Arrays.asList("counter_1", "gauge_2"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertTrue(collectCalled[0]); - Assert.assertEquals(1, snapshots.size()); - Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName()); - } + MetricSnapshots snapshots = registry.scrape(includedNames); + Assert.assertTrue(collectCalled[0]); + Assert.assertEquals(1, snapshots.size()); + Assert.assertEquals("counter_1", snapshots.get(0).getMetadata().getName()); + } - @Test - public void testCompleteFilter_CollectCalled() { + @Test + public void testCompleteFilter_CollectCalled() { - includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2"); + includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertTrue(collectCalled[0]); - Assert.assertEquals(0, snapshots.size()); - } + MetricSnapshots snapshots = registry.scrape(includedNames); + Assert.assertTrue(collectCalled[0]); + Assert.assertEquals(0, snapshots.size()); + } - @Test - public void testCompleteFilter_CollectNotCalled() { + @Test + public void testCompleteFilter_CollectNotCalled() { - includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2"); - prometheusNames = Arrays.asList("counter_1", "gauge_2"); + includedNames = name -> !name.equals("counter_1") && !name.equals("gauge_2"); + prometheusNames = Arrays.asList("counter_1", "gauge_2"); - MetricSnapshots snapshots = registry.scrape(includedNames); - Assert.assertFalse(collectCalled[0]); - Assert.assertEquals(0, snapshots.size()); - } + MetricSnapshots snapshots = registry.scrape(includedNames); + Assert.assertFalse(collectCalled[0]); + Assert.assertEquals(0, snapshots.size()); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java index 498d2354b..c5dd287d7 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/registry/PrometheusRegistryTest.java @@ -4,101 +4,140 @@ import io.prometheus.metrics.model.snapshots.GaugeSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshot; import io.prometheus.metrics.model.snapshots.MetricSnapshots; +import java.util.Arrays; +import java.util.List; import org.junit.Assert; import org.junit.Test; public class PrometheusRegistryTest { - Collector noName = () -> GaugeSnapshot.builder() - .name("no_name_gauge") - .build(); + Collector noName = () -> GaugeSnapshot.builder().name("no_name_gauge").build(); - Collector counterA1 = new Collector() { + Collector counterA1 = + new Collector() { @Override public MetricSnapshot collect() { - return CounterSnapshot.builder().name("counter_a").build(); + return CounterSnapshot.builder().name("counter_a").build(); } @Override public String getPrometheusName() { - return "counter_a"; + return "counter_a"; } - }; + }; - Collector counterA2 = new Collector() { + Collector counterA2 = + new Collector() { @Override public MetricSnapshot collect() { - return CounterSnapshot.builder().name("counter.a").build(); + return CounterSnapshot.builder().name("counter.a").build(); } @Override public String getPrometheusName() { - return "counter_a"; + return "counter_a"; } - }; + }; - Collector counterB = new Collector() { + Collector counterB = + new Collector() { @Override public MetricSnapshot collect() { - return CounterSnapshot.builder().name("counter_b").build(); + return CounterSnapshot.builder().name("counter_b").build(); } @Override public String getPrometheusName() { - return "counter_b"; + return "counter_b"; } - }; + }; - Collector gaugeA = new Collector() { + Collector gaugeA = + new Collector() { @Override public MetricSnapshot collect() { - return GaugeSnapshot.builder().name("gauge_a").build(); + return GaugeSnapshot.builder().name("gauge_a").build(); } @Override public String getPrometheusName() { - return "gauge_a"; + return "gauge_a"; } - }; - - @Test - public void registerNoName() { - PrometheusRegistry registry = new PrometheusRegistry(); - // If the collector does not have a name at registration time, there is no conflict during registration. - registry.register(noName); - registry.register(noName); - // However, at scrape time the collector has to provide a metric name, and then we'll get a duplicat name error. - try { - registry.scrape(); - } catch (IllegalStateException e) { - Assert.assertTrue(e.getMessage().contains("duplicate") && e.getMessage().contains("no_name_gauge")); - return; - } - Assert.fail("Expected duplicate name exception"); - } + }; - @Test(expected = IllegalStateException.class) - public void registerDuplicateName() { - PrometheusRegistry registry = new PrometheusRegistry(); - registry.register(counterA1); - registry.register(counterA2); - } + MultiCollector multiCollector = + new MultiCollector() { + @Override + public MetricSnapshots collect() { + return new MetricSnapshots(gaugeA.collect(), counterB.collect()); + } - @Test - public void registerOk() { - PrometheusRegistry registry = new PrometheusRegistry(); - registry.register(counterA1); - registry.register(counterB); - registry.register(gaugeA); - MetricSnapshots snapshots = registry.scrape(); - Assert.assertEquals(3, snapshots.size()); - - registry.unregister(counterB); - snapshots = registry.scrape(); - Assert.assertEquals(2, snapshots.size()); - - registry.register(counterB); - snapshots = registry.scrape(); - Assert.assertEquals(3, snapshots.size()); + @Override + public List getPrometheusNames() { + return Arrays.asList(gaugeA.getPrometheusName(), counterB.getPrometheusName()); + } + }; + + @Test + public void registerNoName() { + PrometheusRegistry registry = new PrometheusRegistry(); + // If the collector does not have a name at registration time, there is no conflict during + // registration. + registry.register(noName); + registry.register(noName); + // However, at scrape time the collector has to provide a metric name, and then we'll get a + // duplicate name error. + try { + registry.scrape(); + } catch (IllegalStateException e) { + Assert.assertTrue( + e.getMessage().contains("duplicate") && e.getMessage().contains("no_name_gauge")); + return; } + Assert.fail("Expected duplicate name exception"); + } + + @Test(expected = IllegalStateException.class) + public void registerDuplicateName() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(counterA1); + registry.register(counterA2); + } + + @Test + public void registerOk() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(counterA1); + registry.register(counterB); + registry.register(gaugeA); + MetricSnapshots snapshots = registry.scrape(); + Assert.assertEquals(3, snapshots.size()); + + registry.unregister(counterB); + snapshots = registry.scrape(); + Assert.assertEquals(2, snapshots.size()); + + registry.register(counterB); + snapshots = registry.scrape(); + Assert.assertEquals(3, snapshots.size()); + } + + @Test(expected = IllegalStateException.class) + public void registerDuplicateMultiCollector() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(multiCollector); + registry.register(multiCollector); + } + + @Test + public void registerOkMultiCollector() { + PrometheusRegistry registry = new PrometheusRegistry(); + registry.register(multiCollector); + MetricSnapshots snapshots = registry.scrape(); + Assert.assertEquals(2, snapshots.size()); + + registry.unregister(multiCollector); + snapshots = registry.scrape(); + Assert.assertEquals(0, snapshots.size()); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java index 5ff838dd7..e8e53e219 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/HistogramSnapshotTest.java @@ -1,269 +1,308 @@ package io.prometheus.metrics.model.snapshots; import io.prometheus.metrics.model.snapshots.HistogramSnapshot.HistogramDataPointSnapshot; -import org.junit.Assert; -import org.junit.Test; - import java.util.Iterator; import java.util.concurrent.TimeUnit; +import org.junit.Assert; +import org.junit.Test; public class HistogramSnapshotTest { - @Test - public void testGoodCaseComplete() { - long createdTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); - long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2); - long exemplarTimestamp = System.currentTimeMillis(); - Exemplar exemplar1 = Exemplar.builder() - .value(129.0) - .traceId("abcabc") - .spanId("defdef") - .labels(Labels.of("status", "200")) - .timestampMillis(exemplarTimestamp) - .build(); - HistogramSnapshot snapshot = HistogramSnapshot.builder() - .name("request_size_bytes") - .help("request sizes in bytes") - .unit(Unit.BYTES) - .dataPoint( - HistogramDataPointSnapshot.builder() - .sum(27000.0) - .nativeSchema(5) - .nativeZeroCount(2) - .nativeZeroThreshold(0.0000001) - .classicHistogramBuckets(ClassicHistogramBuckets.builder() - .bucket(Double.POSITIVE_INFINITY, 0) - .bucket(128.0, 7) - .bucket(1024.0, 15) - .build()) - // The total number of observations in the native and classic histogram - // is consistent (22 observations), but the individual bucket counts don't fit. - // It doesn't matter for this test, but it would be good to use a more consistent - // example in the test. - .nativeBucketsForPositiveValues(NativeHistogramBuckets.builder() - .bucket(1, 12) - .bucket(2, 3) - .bucket(4, 2) - .build()) - .nativeBucketsForNegativeValues(NativeHistogramBuckets.builder() - .bucket(-1, 1) - .bucket(0, 2) - .build()) - .labels(Labels.of("path", "/")) - .exemplars(Exemplars.of(exemplar1)) - .createdTimestampMillis(createdTimestamp) - .scrapeTimestampMillis(scrapeTimestamp) - .build()) - .dataPoint(HistogramDataPointSnapshot.builder() - .count(3) // TODO how is that not a compile error? This is a protected method! - .sum(400.2) - .nativeSchema(5) - .classicHistogramBuckets(ClassicHistogramBuckets.builder() - .bucket(128.0, 0) - .bucket(1024.0, 4) - .bucket(Double.POSITIVE_INFINITY, 2) - .build()) - .nativeBucketsForPositiveValues(NativeHistogramBuckets.builder() - .bucket(-1, 1) - .bucket(3, 3) - .bucket(4, 2) - .build()) - .labels(Labels.of("path", "/api/v1")) - .exemplars(Exemplars.of(exemplar1)) - .createdTimestampMillis(createdTimestamp) - .scrapeTimestampMillis(scrapeTimestamp) - .build()) - .build(); - SnapshotTestUtil.assertMetadata(snapshot, "request_size_bytes", "request sizes in bytes", "bytes"); + @Test + public void testGoodCaseComplete() { + long createdTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); + long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2); + long exemplarTimestamp = System.currentTimeMillis(); + Exemplar exemplar1 = + Exemplar.builder() + .value(129.0) + .traceId("abcabc") + .spanId("defdef") + .labels(Labels.of("status", "200")) + .timestampMillis(exemplarTimestamp) + .build(); + HistogramSnapshot snapshot = + HistogramSnapshot.builder() + .name("request_size_bytes") + .help("request sizes in bytes") + .unit(Unit.BYTES) + .dataPoint( + HistogramDataPointSnapshot.builder() + .sum(27000.0) + .nativeSchema(5) + .nativeZeroCount(2) + .nativeZeroThreshold(0.0000001) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(Double.POSITIVE_INFINITY, 0) + .bucket(128.0, 7) + .bucket(1024.0, 15) + .build()) + // The total number of observations in the native and classic histogram + // is consistent (22 observations), but the individual bucket counts don't fit. + // It doesn't matter for this test, but it would be good to use a more + // consistent + // example in the test. + .nativeBucketsForPositiveValues( + NativeHistogramBuckets.builder() + .bucket(1, 12) + .bucket(2, 3) + .bucket(4, 2) + .build()) + .nativeBucketsForNegativeValues( + NativeHistogramBuckets.builder().bucket(-1, 1).bucket(0, 2).build()) + .labels(Labels.of("path", "/")) + .exemplars(Exemplars.of(exemplar1)) + .createdTimestampMillis(createdTimestamp) + .scrapeTimestampMillis(scrapeTimestamp) + .build()) + .dataPoint( + HistogramDataPointSnapshot.builder() + .count(3) + .sum(400.2) + .nativeSchema(5) + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(128.0, 0) + .bucket(1024.0, 4) + .bucket(Double.POSITIVE_INFINITY, 2) + .build()) + .nativeBucketsForPositiveValues( + NativeHistogramBuckets.builder() + .bucket(-1, 1) + .bucket(3, 3) + .bucket(4, 2) + .build()) + .labels(Labels.of("path", "/api/v1")) + .exemplars(Exemplars.of(exemplar1)) + .createdTimestampMillis(createdTimestamp) + .scrapeTimestampMillis(scrapeTimestamp) + .build()) + .build(); + SnapshotTestUtil.assertMetadata( + snapshot, "request_size_bytes", "request sizes in bytes", "bytes"); - Assert.assertEquals(2, snapshot.getDataPoints().size()); - HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); // data is sorted by labels, so the first one should be path="/" - Assert.assertTrue(data.hasSum()); - Assert.assertTrue(data.hasCount()); - Assert.assertTrue(data.hasCreatedTimestamp()); - Assert.assertTrue(data.hasScrapeTimestamp()); - Assert.assertEquals(22, data.getCount()); - Assert.assertEquals(27000.0, data.getSum(), 0.0); - Assert.assertEquals(Labels.of("path", "/"), data.getLabels()); - Assert.assertEquals(exemplar1.getValue(), data.getExemplars().get(128.0, 1024.0).getValue(), 0.0); - Assert.assertEquals(createdTimestamp, data.getCreatedTimestampMillis()); - Assert.assertEquals(scrapeTimestamp, data.getScrapeTimestampMillis()); - // classic histogram 1 - int i = 0; - for (ClassicHistogramBucket bucket : data.getClassicBuckets()) { - switch (i++) { - case 0: - Assert.assertEquals(128.0, bucket.getUpperBound(), 0.0); - Assert.assertEquals(7, bucket.getCount()); - break; - case 1: - Assert.assertEquals(1024.0, bucket.getUpperBound(), 0.0); - Assert.assertEquals(15, bucket.getCount()); - break; - case 2: - Assert.assertEquals(Double.POSITIVE_INFINITY, bucket.getUpperBound(), 0.0); - Assert.assertEquals(0, bucket.getCount()); - break; - } - } - Assert.assertEquals("expecting 3 classic histogram buckets", 3, i); - // native histogram 1 - Assert.assertEquals(5, data.getNativeSchema()); - Assert.assertEquals(2, data.getNativeZeroCount()); - Assert.assertEquals(0.0000001, data.getNativeZeroThreshold(), 0.0000001); - Assert.assertEquals(3, data.getNativeBucketsForPositiveValues().size()); - i = 0; - for (NativeHistogramBucket bucket : data.getNativeBucketsForPositiveValues()) { - switch (i++) { - case 0: - Assert.assertEquals(1, bucket.getBucketIndex()); - Assert.assertEquals(12, bucket.getCount()); - break; - case 1: - Assert.assertEquals(2, bucket.getBucketIndex()); - Assert.assertEquals(3, bucket.getCount()); - break; - case 2: - Assert.assertEquals(4, bucket.getBucketIndex()); - Assert.assertEquals(2, bucket.getCount()); - break; - } - } - Assert.assertEquals("expecting 3 native buckets for positive values", 3, i); - i = 0; - Assert.assertEquals(2, data.getNativeBucketsForNegativeValues().size()); - for (NativeHistogramBucket bucket : data.getNativeBucketsForNegativeValues()) { - switch (i++) { - case 0: - Assert.assertEquals(-1, bucket.getBucketIndex()); - Assert.assertEquals(1, bucket.getCount()); - break; - case 1: - Assert.assertEquals(0, bucket.getBucketIndex()); - Assert.assertEquals(2, bucket.getCount()); - break; - } - } - Assert.assertEquals("expecting 2 native buckets for positive values", 2, i); - // classic histogram 2 (it's ok that this is incomplete, because we covered it with the other tests) - data = snapshot.getDataPoints().get(1); - Assert.assertEquals(6, data.getCount()); - // native histogram 2 (it's ok that this is incomplete, because we covered it with the other tests) - Assert.assertEquals(5, data.getNativeSchema()); - Assert.assertEquals(0, data.getNativeZeroCount()); - Assert.assertEquals(0, data.getNativeZeroThreshold(), 0); + Assert.assertEquals(2, snapshot.getDataPoints().size()); + HistogramDataPointSnapshot data = + snapshot + .getDataPoints() + .get(0); // data is sorted by labels, so the first one should be path="/" + Assert.assertTrue(data.hasSum()); + Assert.assertTrue(data.hasCount()); + Assert.assertTrue(data.hasCreatedTimestamp()); + Assert.assertTrue(data.hasScrapeTimestamp()); + Assert.assertEquals(22, data.getCount()); + Assert.assertEquals(27000.0, data.getSum(), 0.0); + Assert.assertEquals(Labels.of("path", "/"), data.getLabels()); + Assert.assertEquals( + exemplar1.getValue(), data.getExemplars().get(128.0, 1024.0).getValue(), 0.0); + Assert.assertEquals(createdTimestamp, data.getCreatedTimestampMillis()); + Assert.assertEquals(scrapeTimestamp, data.getScrapeTimestampMillis()); + // classic histogram 1 + int i = 0; + for (ClassicHistogramBucket bucket : data.getClassicBuckets()) { + switch (i++) { + case 0: + Assert.assertEquals(128.0, bucket.getUpperBound(), 0.0); + Assert.assertEquals(7, bucket.getCount()); + break; + case 1: + Assert.assertEquals(1024.0, bucket.getUpperBound(), 0.0); + Assert.assertEquals(15, bucket.getCount()); + break; + case 2: + Assert.assertEquals(Double.POSITIVE_INFINITY, bucket.getUpperBound(), 0.0); + Assert.assertEquals(0, bucket.getCount()); + break; + } } - - @Test - public void testEmptyHistogram() { - HistogramSnapshot snapshot = HistogramSnapshot.builder() - .name("empty_histogram") - .build(); - Assert.assertEquals(0, snapshot.getDataPoints().size()); + Assert.assertEquals("expecting 3 classic histogram buckets", 3, i); + // native histogram 1 + Assert.assertEquals(5, data.getNativeSchema()); + Assert.assertEquals(2, data.getNativeZeroCount()); + Assert.assertEquals(0.0000001, data.getNativeZeroThreshold(), 0.0000001); + Assert.assertEquals(3, data.getNativeBucketsForPositiveValues().size()); + i = 0; + for (NativeHistogramBucket bucket : data.getNativeBucketsForPositiveValues()) { + switch (i++) { + case 0: + Assert.assertEquals(1, bucket.getBucketIndex()); + Assert.assertEquals(12, bucket.getCount()); + break; + case 1: + Assert.assertEquals(2, bucket.getBucketIndex()); + Assert.assertEquals(3, bucket.getCount()); + break; + case 2: + Assert.assertEquals(4, bucket.getBucketIndex()); + Assert.assertEquals(2, bucket.getCount()); + break; + } } - - @Test - public void testMinimalClassicHistogram() { - HistogramSnapshot snapshot = HistogramSnapshot.builder() - .name("minimal_histogram") - .dataPoint(HistogramDataPointSnapshot.builder() - .classicHistogramBuckets(ClassicHistogramBuckets.of(new double[]{Double.POSITIVE_INFINITY}, new long[]{0})) - .build()) - .build(); - HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); - Assert.assertFalse(data.hasSum()); - Assert.assertEquals(1, snapshot.getDataPoints().get(0).getClassicBuckets().size()); + Assert.assertEquals("expecting 3 native buckets for positive values", 3, i); + i = 0; + Assert.assertEquals(2, data.getNativeBucketsForNegativeValues().size()); + for (NativeHistogramBucket bucket : data.getNativeBucketsForNegativeValues()) { + switch (i++) { + case 0: + Assert.assertEquals(-1, bucket.getBucketIndex()); + Assert.assertEquals(1, bucket.getCount()); + break; + case 1: + Assert.assertEquals(0, bucket.getBucketIndex()); + Assert.assertEquals(2, bucket.getCount()); + break; + } } + Assert.assertEquals("expecting 2 native buckets for positive values", 2, i); + // classic histogram 2 (it's ok that this is incomplete, because we covered it with the other + // tests) + data = snapshot.getDataPoints().get(1); + Assert.assertEquals(6, data.getCount()); + // native histogram 2 (it's ok that this is incomplete, because we covered it with the other + // tests) + Assert.assertEquals(5, data.getNativeSchema()); + Assert.assertEquals(0, data.getNativeZeroCount()); + Assert.assertEquals(0, data.getNativeZeroThreshold(), 0); + } - @Test - public void testMinimalNativeHistogram() { - HistogramSnapshot snapshot = HistogramSnapshot.builder() - .name("hist") - .dataPoint(HistogramDataPointSnapshot.builder() - .nativeSchema(5) - .build()) - .build(); - Assert.assertEquals("hist", snapshot.getMetadata().getName()); - Assert.assertFalse(snapshot.getMetadata().hasUnit()); - Assert.assertEquals(1, snapshot.getDataPoints().size()); - HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); - Assert.assertFalse(data.hasCreatedTimestamp()); - Assert.assertFalse(data.hasScrapeTimestamp()); - Assert.assertTrue(data.hasCount()); - Assert.assertEquals(0, data.getCount()); - Assert.assertFalse(data.hasSum()); - Assert.assertEquals(0, data.getNativeBucketsForNegativeValues().size()); - Assert.assertEquals(0, data.getNativeBucketsForPositiveValues().size()); - } + @Test + public void testEmptyHistogram() { + HistogramSnapshot snapshot = HistogramSnapshot.builder().name("empty_histogram").build(); + Assert.assertEquals(0, snapshot.getDataPoints().size()); + } - @Test - public void testClassicCount() { - HistogramSnapshot snapshot = HistogramSnapshot.builder() - .name("test_histogram") - .dataPoint(HistogramDataPointSnapshot.builder() - .classicHistogramBuckets(ClassicHistogramBuckets.builder() - .bucket(1.0, 3) - .bucket(2.0, 2) - .bucket(Double.POSITIVE_INFINITY, 0) - .build()) - .build()) - .build(); - HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); - Assert.assertFalse(data.hasSum()); - Assert.assertTrue(data.hasCount()); - Assert.assertEquals(5, data.getCount()); - } + @Test + public void testMinimalClassicHistogram() { + HistogramSnapshot snapshot = + HistogramSnapshot.builder() + .name("minimal_histogram") + .dataPoint( + HistogramDataPointSnapshot.builder() + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {0})) + .build()) + .build(); + HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + Assert.assertFalse(data.hasSum()); + Assert.assertEquals(1, snapshot.getDataPoints().get(0).getClassicBuckets().size()); + } - @Test(expected = IllegalArgumentException.class) - public void testEmptyData() { - // This will fail because one of nativeSchema and classicHistogramBuckets is required - HistogramDataPointSnapshot.builder().build(); - } + @Test + public void testMinimalNativeHistogram() { + HistogramSnapshot snapshot = + HistogramSnapshot.builder() + .name("hist") + .dataPoint(HistogramDataPointSnapshot.builder().nativeSchema(5).build()) + .build(); + Assert.assertEquals("hist", snapshot.getMetadata().getName()); + Assert.assertFalse(snapshot.getMetadata().hasUnit()); + Assert.assertEquals(1, snapshot.getDataPoints().size()); + HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + Assert.assertFalse(data.hasCreatedTimestamp()); + Assert.assertFalse(data.hasScrapeTimestamp()); + Assert.assertTrue(data.hasCount()); + Assert.assertEquals(0, data.getCount()); + Assert.assertFalse(data.hasSum()); + Assert.assertEquals(0, data.getNativeBucketsForNegativeValues().size()); + Assert.assertEquals(0, data.getNativeBucketsForPositiveValues().size()); + } - @Test - public void testEmptyNativeData() { - HistogramDataPointSnapshot data = HistogramDataPointSnapshot.builder() - .nativeSchema(5) - .build(); - Assert.assertEquals(0, data.getNativeBucketsForNegativeValues().size()); - Assert.assertEquals(0, data.getNativeBucketsForPositiveValues().size()); - } + @Test + public void testClassicCount() { + HistogramSnapshot snapshot = + HistogramSnapshot.builder() + .name("test_histogram") + .dataPoint( + HistogramDataPointSnapshot.builder() + .classicHistogramBuckets( + ClassicHistogramBuckets.builder() + .bucket(1.0, 3) + .bucket(2.0, 2) + .bucket(Double.POSITIVE_INFINITY, 0) + .build()) + .build()) + .build(); + HistogramDataPointSnapshot data = snapshot.getDataPoints().get(0); + Assert.assertFalse(data.hasSum()); + Assert.assertTrue(data.hasCount()); + Assert.assertEquals(5, data.getCount()); + } - @Test(expected = UnsupportedOperationException.class) - public void testDataImmutable() { - HistogramSnapshot snapshot = HistogramSnapshot.builder() - .name("test_histogram") - .dataPoint(HistogramDataPointSnapshot.builder() - .labels(Labels.of("a", "a")) - .classicHistogramBuckets(ClassicHistogramBuckets.of(new double[]{Double.POSITIVE_INFINITY}, new long[]{0})) - .build()) - .dataPoint(HistogramDataPointSnapshot.builder() - .labels(Labels.of("a", "b")) - .classicHistogramBuckets(ClassicHistogramBuckets.of(new double[]{Double.POSITIVE_INFINITY}, new long[]{2})) - .build()) - .build(); - Iterator iterator = snapshot.getDataPoints().iterator(); - iterator.next(); - iterator.remove(); - } + @Test(expected = IllegalArgumentException.class) + public void testEmptyData() { + // This will fail because one of nativeSchema and classicHistogramBuckets is required + HistogramDataPointSnapshot.builder().build(); + } - @Test(expected = IllegalArgumentException.class) - public void testEmptyClassicBuckets() { - new HistogramDataPointSnapshot(ClassicHistogramBuckets.EMPTY, Double.NaN, Labels.EMPTY, Exemplars.EMPTY, 0L); - } + @Test + public void testEmptyNativeData() { + HistogramDataPointSnapshot data = HistogramDataPointSnapshot.builder().nativeSchema(5).build(); + Assert.assertEquals(0, data.getNativeBucketsForNegativeValues().size()); + Assert.assertEquals(0, data.getNativeBucketsForPositiveValues().size()); + } - @Test - public void testMinimalNativeData() { - new HistogramDataPointSnapshot(ClassicHistogramBuckets.EMPTY, 0, 0, 0.0, - NativeHistogramBuckets.EMPTY, NativeHistogramBuckets.EMPTY, Double.NaN, Labels.EMPTY, Exemplars.EMPTY, 0L); - } + @Test(expected = UnsupportedOperationException.class) + public void testDataImmutable() { + HistogramSnapshot snapshot = + HistogramSnapshot.builder() + .name("test_histogram") + .dataPoint( + HistogramDataPointSnapshot.builder() + .labels(Labels.of("a", "a")) + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {0})) + .build()) + .dataPoint( + HistogramDataPointSnapshot.builder() + .labels(Labels.of("a", "b")) + .classicHistogramBuckets( + ClassicHistogramBuckets.of( + new double[] {Double.POSITIVE_INFINITY}, new long[] {2})) + .build()) + .build(); + Iterator iterator = snapshot.getDataPoints().iterator(); + iterator.next(); + iterator.remove(); + } - @Test - public void testMinimalClassicData() { - ClassicHistogramBuckets buckets = ClassicHistogramBuckets.builder() - .bucket(Double.POSITIVE_INFINITY, 0) - .build(); - new HistogramDataPointSnapshot(buckets, HistogramSnapshot.CLASSIC_HISTOGRAM, 0, 0.0, - NativeHistogramBuckets.EMPTY, NativeHistogramBuckets.EMPTY, Double.NaN, Labels.EMPTY, Exemplars.EMPTY, 0L); - } + @Test(expected = IllegalArgumentException.class) + public void testEmptyClassicBuckets() { + new HistogramDataPointSnapshot( + ClassicHistogramBuckets.EMPTY, Double.NaN, Labels.EMPTY, Exemplars.EMPTY, 0L); + } + + @Test + public void testMinimalNativeData() { + new HistogramDataPointSnapshot( + ClassicHistogramBuckets.EMPTY, + 0, + 0, + 0.0, + NativeHistogramBuckets.EMPTY, + NativeHistogramBuckets.EMPTY, + Double.NaN, + Labels.EMPTY, + Exemplars.EMPTY, + 0L); + } + + @Test + public void testMinimalClassicData() { + ClassicHistogramBuckets buckets = + ClassicHistogramBuckets.builder().bucket(Double.POSITIVE_INFINITY, 0).build(); + new HistogramDataPointSnapshot( + buckets, + HistogramSnapshot.CLASSIC_HISTOGRAM, + 0, + 0.0, + NativeHistogramBuckets.EMPTY, + NativeHistogramBuckets.EMPTY, + Double.NaN, + Labels.EMPTY, + Exemplars.EMPTY, + 0L); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java index d7a3e8b3e..e7ae37723 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/LabelsTest.java @@ -1,131 +1,128 @@ package io.prometheus.metrics.model.snapshots; -import io.prometheus.metrics.model.snapshots.Labels; +import static org.junit.Assert.assertNotEquals; + import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeLabelName; -import static org.junit.Assert.assertNotEquals; - public class LabelsTest { - private > void assertLessThan(T a, T b) { - Assert.assertTrue(a.compareTo(b) < 0); - } - - private > void assertGreaterThan(T a, T b) { - Assert.assertTrue(a.compareTo(b) > 0); - } - - @Test - public void testCompareDifferentLabelNames() { - Labels labels1 = Labels.of("env", "prod", "status2", "200"); - Labels labels2 = Labels.of("env", "prod", "status1", "200"); - assertGreaterThan(labels1, labels2); - assertLessThan(labels2, labels1); - assertNotEquals(labels1, labels2); - assertNotEquals(labels2, labels1); - } - - @Test - public void testCompareSameLabelNames() { - // If all label names are the same, labels should be sorted by label value. - Labels labels1 = Labels.of("env", "prod", "status", "200"); - Labels labels2 = Labels.of("env", "prod", "status", "500"); - assertLessThan(labels1, labels2); - assertGreaterThan(labels2, labels1); - assertNotEquals(labels1, labels2); - assertNotEquals(labels2, labels1); - } - - @Test - public void testCompareDifferentNumberOfLabels() { - Labels labels1 = Labels.of("env", "prod", "status", "200"); - Labels labels2 = Labels.of("env", "prod", "status", "200", "x_code", "none"); - assertLessThan(labels1, labels2); - assertGreaterThan(labels2, labels1); - assertNotEquals(labels1, labels2); - assertNotEquals(labels2, labels1); - } - - @Test - public void testComparePrometheusNames() { - Labels labels1 = Labels.of("my_a", "val"); - Labels labels2 = Labels.of("my.b", "val"); - assertLessThan(labels1, labels2); // this is true because it compares "my_a" to "my_b". - } - - @Test - public void testEqualsHashcodeDots() { - Labels labels1 = Labels.of("my_a", "val"); - Labels labels2 = Labels.of("my.a", "val"); - Assert.assertEquals(labels1, labels2); - Assert.assertEquals(labels1.hashCode(), labels2.hashCode()); - } - - @Test - public void testCompareEquals() { - Labels labels1 = Labels.of("env", "prod", "status", "200"); - Labels labels2 = Labels.of("env", "prod", "status", "200"); - Assert.assertEquals(0, labels1.compareTo(labels2)); - Assert.assertEquals(0, labels2.compareTo(labels1)); - Assert.assertEquals(labels1, labels2); - Assert.assertEquals(labels2, labels1); - } - - @Test(expected = IllegalArgumentException.class) - public void testIllegalLabelName() { - Labels.of("my_service/status", "200"); - } - - @Test(expected = IllegalArgumentException.class) - public void testReservedLabelName() { - Labels.of("__name__", "requests_total"); - } - - @Test(expected = IllegalArgumentException.class) - public void testDuplicateLabelName() { - Labels.of("name1", "value1", "name2", "value2", "name1", "value3"); - } - - @Test - public void testMakePrometheusNames() { - String[] names = new String[]{}; - String[] prometheusNames = Labels.makePrometheusNames(names); - Assert.assertSame(names, prometheusNames); - - names = new String[]{"no_dots", "at_all"}; - prometheusNames = Labels.makePrometheusNames(names); - Assert.assertSame(names, prometheusNames); - - names = new String[]{"dots", "here.it.is"}; - prometheusNames = Labels.makePrometheusNames(names); - Assert.assertNotSame(names, prometheusNames); - Assert.assertSame(names[0], prometheusNames[0]); - Assert.assertEquals("here.it.is", names[1]); - Assert.assertEquals("here_it_is", prometheusNames[1]); - } - - @Test - public void testMerge() { - Labels labels1 = Labels.of("key.1", "value 1", "key.3", "value 3"); - Labels labels2 = Labels.of("key_2", "value 2"); - Labels merged = labels2.merge(labels1); - Assert.assertEquals("key.1", merged.getName(0)); - Assert.assertEquals("key_2", merged.getName(1)); - Assert.assertEquals("key.3", merged.getName(2)); - } - - @Test(expected = IllegalArgumentException.class) - public void testMergeDuplicateName() { - Labels labels1 = Labels.of("key_one", "v1"); - Labels labels2 = Labels.of("key.one", "v2"); - labels2.merge(labels1); - } - - @Test(expected = IllegalArgumentException.class) - public void testDuplicateName() { - Labels.of("key_one", "v1", "key.one", "v2"); - } + private > void assertLessThan(T a, T b) { + Assert.assertTrue(a.compareTo(b) < 0); + } + + private > void assertGreaterThan(T a, T b) { + Assert.assertTrue(a.compareTo(b) > 0); + } + + @Test + public void testCompareDifferentLabelNames() { + Labels labels1 = Labels.of("env", "prod", "status2", "200"); + Labels labels2 = Labels.of("env", "prod", "status1", "200"); + assertGreaterThan(labels1, labels2); + assertLessThan(labels2, labels1); + assertNotEquals(labels1, labels2); + assertNotEquals(labels2, labels1); + } + + @Test + public void testCompareSameLabelNames() { + // If all label names are the same, labels should be sorted by label value. + Labels labels1 = Labels.of("env", "prod", "status", "200"); + Labels labels2 = Labels.of("env", "prod", "status", "500"); + assertLessThan(labels1, labels2); + assertGreaterThan(labels2, labels1); + assertNotEquals(labels1, labels2); + assertNotEquals(labels2, labels1); + } + + @Test + public void testCompareDifferentNumberOfLabels() { + Labels labels1 = Labels.of("env", "prod", "status", "200"); + Labels labels2 = Labels.of("env", "prod", "status", "200", "x_code", "none"); + assertLessThan(labels1, labels2); + assertGreaterThan(labels2, labels1); + assertNotEquals(labels1, labels2); + assertNotEquals(labels2, labels1); + } + + @Test + public void testComparePrometheusNames() { + Labels labels1 = Labels.of("my_a", "val"); + Labels labels2 = Labels.of("my.b", "val"); + assertLessThan(labels1, labels2); // this is true because it compares "my_a" to "my_b". + } + + @Test + public void testEqualsHashcodeDots() { + Labels labels1 = Labels.of("my_a", "val"); + Labels labels2 = Labels.of("my.a", "val"); + Assert.assertEquals(labels1, labels2); + Assert.assertEquals(labels1.hashCode(), labels2.hashCode()); + } + + @Test + public void testCompareEquals() { + Labels labels1 = Labels.of("env", "prod", "status", "200"); + Labels labels2 = Labels.of("env", "prod", "status", "200"); + Assert.assertEquals(0, labels1.compareTo(labels2)); + Assert.assertEquals(0, labels2.compareTo(labels1)); + Assert.assertEquals(labels1, labels2); + Assert.assertEquals(labels2, labels1); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalLabelName() { + Labels.of("my_service/status", "200"); + } + + @Test(expected = IllegalArgumentException.class) + public void testReservedLabelName() { + Labels.of("__name__", "requests_total"); + } + + @Test(expected = IllegalArgumentException.class) + public void testDuplicateLabelName() { + Labels.of("name1", "value1", "name2", "value2", "name1", "value3"); + } + + @Test + public void testMakePrometheusNames() { + String[] names = new String[] {}; + String[] prometheusNames = Labels.makePrometheusNames(names); + Assert.assertSame(names, prometheusNames); + + names = new String[] {"no_dots", "at_all"}; + prometheusNames = Labels.makePrometheusNames(names); + Assert.assertSame(names, prometheusNames); + + names = new String[] {"dots", "here.it.is"}; + prometheusNames = Labels.makePrometheusNames(names); + Assert.assertNotSame(names, prometheusNames); + Assert.assertSame(names[0], prometheusNames[0]); + Assert.assertEquals("here.it.is", names[1]); + Assert.assertEquals("here_it_is", prometheusNames[1]); + } + + @Test + public void testMerge() { + Labels labels1 = Labels.of("key.1", "value 1", "key.3", "value 3"); + Labels labels2 = Labels.of("key_2", "value 2"); + Labels merged = labels2.merge(labels1); + Assert.assertEquals("key.1", merged.getName(0)); + Assert.assertEquals("key_2", merged.getName(1)); + Assert.assertEquals("key.3", merged.getName(2)); + } + + @Test(expected = IllegalArgumentException.class) + public void testMergeDuplicateName() { + Labels labels1 = Labels.of("key_one", "v1"); + Labels labels2 = Labels.of("key.one", "v2"); + labels2.merge(labels1); + } + + @Test(expected = IllegalArgumentException.class) + public void testDuplicateName() { + Labels.of("key_one", "v1", "key.one", "v2"); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index f9e9a18d6..9a3a980bc 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -1,72 +1,76 @@ package io.prometheus.metrics.model.snapshots; +import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; + import org.junit.Assert; import org.junit.Test; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName; -import static io.prometheus.metrics.model.snapshots.PrometheusNaming.sanitizeMetricName; - public class MetricMetadataTest { - @Test(expected = IllegalArgumentException.class) - public void testEmptyName() { - new MetricMetadata(""); - } + @Test(expected = IllegalArgumentException.class) + public void testEmptyName() { + new MetricMetadata(""); + } - @Test(expected = IllegalArgumentException.class) - public void testNullName() { - new MetricMetadata(null); - } + @Test(expected = IllegalArgumentException.class) + public void testNullName() { + new MetricMetadata(null); + } - @Test(expected = IllegalArgumentException.class) - public void testIllegalName() { - new MetricMetadata("my_namespace/http_server_duration"); // let's see when we decide to allow slashes :) - } + @Test(expected = IllegalArgumentException.class) + public void testIllegalName() { + new MetricMetadata( + "my_namespace/http_server_duration"); // let's see when we decide to allow slashes :) + } - @Test - public void testSanitizationIllegalCharacters() { - MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("my_namespace/http.server.duration", Unit.SECONDS), "help string", Unit.SECONDS); - Assert.assertEquals("my_namespace_http.server.duration_seconds", metadata.getName()); - Assert.assertEquals("my_namespace_http_server_duration_seconds", metadata.getPrometheusName()); - Assert.assertEquals("help string", metadata.getHelp()); - Assert.assertEquals("seconds", metadata.getUnit().toString()); - } + @Test + public void testSanitizationIllegalCharacters() { + MetricMetadata metadata = + new MetricMetadata( + sanitizeMetricName("my_namespace/http.server.duration", Unit.SECONDS), + "help string", + Unit.SECONDS); + Assert.assertEquals("my_namespace_http.server.duration_seconds", metadata.getName()); + Assert.assertEquals("my_namespace_http_server_duration_seconds", metadata.getPrometheusName()); + Assert.assertEquals("help string", metadata.getHelp()); + Assert.assertEquals("seconds", metadata.getUnit().toString()); + } - @Test - public void testSanitizationCounter() { - MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("my_events_total")); - Assert.assertEquals("my_events", metadata.getName()); - } + @Test + public void testSanitizationCounter() { + MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("my_events_total")); + Assert.assertEquals("my_events", metadata.getName()); + } - @Test - public void testSanitizationInfo() { - MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("target_info")); - Assert.assertEquals("target", metadata.getName()); - } + @Test + public void testSanitizationInfo() { + MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("target_info")); + Assert.assertEquals("target", metadata.getName()); + } - @Test - public void testSanitizationWeirdCornerCase() { - MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("_total_created")); - Assert.assertEquals("total", metadata.getName()); - } + @Test + public void testSanitizationWeirdCornerCase() { + MetricMetadata metadata = new MetricMetadata(sanitizeMetricName("_total_created")); + Assert.assertEquals("total", metadata.getName()); + } - @Test(expected = IllegalArgumentException.class) - public void testSanitizeEmptyString() { - sanitizeMetricName(""); - } + @Test(expected = IllegalArgumentException.class) + public void testSanitizeEmptyString() { + sanitizeMetricName(""); + } - @Test(expected = IllegalArgumentException.class) - public void testUnitSuffixRequired() { - new MetricMetadata("my_counter", "help", Unit.SECONDS); - } + @Test(expected = IllegalArgumentException.class) + public void testUnitSuffixRequired() { + new MetricMetadata("my_counter", "help", Unit.SECONDS); + } - @Test - public void testUnitSuffixAdded() { - new MetricMetadata(sanitizeMetricName("my_counter", Unit.SECONDS), "help", Unit.SECONDS); - } + @Test + public void testUnitSuffixAdded() { + new MetricMetadata(sanitizeMetricName("my_counter", Unit.SECONDS), "help", Unit.SECONDS); + } - @Test - public void testUnitNotDuplicated() { - Assert.assertEquals("my_counter_bytes", sanitizeMetricName("my_counter_bytes", Unit.BYTES)); - } + @Test + public void testUnitNotDuplicated() { + Assert.assertEquals("my_counter_bytes", sanitizeMetricName("my_counter_bytes", Unit.BYTES)); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java index 54766debd..1a531b5fd 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/SnapshotTestUtil.java @@ -1,17 +1,17 @@ package io.prometheus.metrics.model.snapshots; -import io.prometheus.metrics.model.snapshots.MetricSnapshot; import org.junit.Assert; public class SnapshotTestUtil { - public static void assertMetadata(MetricSnapshot snapshot, String name, String help, String unit) { - Assert.assertEquals(name, snapshot.getMetadata().getName()); - Assert.assertEquals(help, snapshot.getMetadata().getHelp()); - if (unit != null) { - Assert.assertEquals(unit, snapshot.getMetadata().getUnit().toString()); - } else { - Assert.assertNull(snapshot.getMetadata().getUnit()); - } + public static void assertMetadata( + MetricSnapshot snapshot, String name, String help, String unit) { + Assert.assertEquals(name, snapshot.getMetadata().getName()); + Assert.assertEquals(help, snapshot.getMetadata().getHelp()); + if (unit != null) { + Assert.assertEquals(unit, snapshot.getMetadata().getUnit().toString()); + } else { + Assert.assertNull(snapshot.getMetadata().getUnit()); } + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java index 2795ec165..ccc159b30 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/StateSetSnapshotTest.java @@ -1,145 +1,145 @@ package io.prometheus.metrics.model.snapshots; -import org.junit.Assert; -import org.junit.Test; - import java.util.Iterator; import java.util.concurrent.TimeUnit; +import org.junit.Assert; +import org.junit.Test; public class StateSetSnapshotTest { - @Test - public void testCompleteGoodCase() { - long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); - StateSetSnapshot snapshot = StateSetSnapshot.builder() - .name("my_feature_flags") - .help("Feature Flags") - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(Labels.of("entity", "controller")) - .scrapeTimestampMillis(scrapeTimestamp) - .state("feature1", true) - .state("feature2", false) - .build() - ) - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(Labels.of("entity", "api")) - .state("feature1", false) - .state("feature2", false) - .build() - ) - .build(); - SnapshotTestUtil.assertMetadata(snapshot, "my_feature_flags", "Feature Flags", null); - Assert.assertEquals(2, snapshot.getDataPoints().size()); - StateSetSnapshot.StateSetDataPointSnapshot data = snapshot.getDataPoints().get(1); // data is sorted by labels, so the second one should be entity="controller" - Assert.assertEquals(Labels.of("entity", "controller"), data.getLabels()); - Assert.assertEquals(2, data.size()); - Assert.assertEquals("feature1", data.getName(0)); - Assert.assertTrue(data.isTrue(0)); - Assert.assertEquals("feature2", data.getName(1)); - Assert.assertFalse(data.isTrue(1)); - Assert.assertTrue(data.hasScrapeTimestamp()); - Assert.assertEquals(scrapeTimestamp, data.getScrapeTimestampMillis()); - Assert.assertFalse(data.hasCreatedTimestamp()); - } - - @Test - public void testStateSetDataSorted() { - StateSetSnapshot.StateSetDataPointSnapshot data = StateSetSnapshot.StateSetDataPointSnapshot.builder() - .state("b", true) - .state("d", false) - .state("c", true) - .state("a", false) - .build(); - Assert.assertEquals(4, data.size()); - Assert.assertEquals("a", data.getName(0)); - Assert.assertFalse(data.isTrue(0)); - Assert.assertEquals("b", data.getName(1)); - Assert.assertTrue(data.isTrue(1)); - Assert.assertEquals("c", data.getName(2)); - Assert.assertTrue(data.isTrue(2)); - Assert.assertEquals("d", data.getName(3)); - Assert.assertFalse(data.isTrue(3)); - } + @Test + public void testCompleteGoodCase() { + long scrapeTimestamp = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1); + StateSetSnapshot snapshot = + StateSetSnapshot.builder() + .name("my_feature_flags") + .help("Feature Flags") + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(Labels.of("entity", "controller")) + .scrapeTimestampMillis(scrapeTimestamp) + .state("feature1", true) + .state("feature2", false) + .build()) + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(Labels.of("entity", "api")) + .state("feature1", false) + .state("feature2", false) + .build()) + .build(); + SnapshotTestUtil.assertMetadata(snapshot, "my_feature_flags", "Feature Flags", null); + Assert.assertEquals(2, snapshot.getDataPoints().size()); + StateSetSnapshot.StateSetDataPointSnapshot data = + snapshot + .getDataPoints() + .get(1); // data is sorted by labels, so the second one should be entity="controller" + Assert.assertEquals(Labels.of("entity", "controller"), data.getLabels()); + Assert.assertEquals(2, data.size()); + Assert.assertEquals("feature1", data.getName(0)); + Assert.assertTrue(data.isTrue(0)); + Assert.assertEquals("feature2", data.getName(1)); + Assert.assertFalse(data.isTrue(1)); + Assert.assertTrue(data.hasScrapeTimestamp()); + Assert.assertEquals(scrapeTimestamp, data.getScrapeTimestampMillis()); + Assert.assertFalse(data.hasCreatedTimestamp()); + } - @Test(expected = IllegalArgumentException.class) - public void testMustHaveState() { - // Must have at least one state. - StateSetSnapshot.StateSetDataPointSnapshot.builder().build(); - } + @Test + public void testStateSetDataSorted() { + StateSetSnapshot.StateSetDataPointSnapshot data = + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .state("b", true) + .state("d", false) + .state("c", true) + .state("a", false) + .build(); + Assert.assertEquals(4, data.size()); + Assert.assertEquals("a", data.getName(0)); + Assert.assertFalse(data.isTrue(0)); + Assert.assertEquals("b", data.getName(1)); + Assert.assertTrue(data.isTrue(1)); + Assert.assertEquals("c", data.getName(2)); + Assert.assertTrue(data.isTrue(2)); + Assert.assertEquals("d", data.getName(3)); + Assert.assertFalse(data.isTrue(3)); + } - @Test - public void testMinimal() { - StateSetSnapshot snapshot = StateSetSnapshot.builder() - .name("my_flag") - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .state("flag", true) - .build() - ) - .build(); - Assert.assertEquals(1, snapshot.dataPoints.size()); - } + @Test(expected = IllegalArgumentException.class) + public void testMustHaveState() { + // Must have at least one state. + StateSetSnapshot.StateSetDataPointSnapshot.builder().build(); + } - @Test - public void testEmpty() { - StateSetSnapshot snapshot = StateSetSnapshot.builder() - .name("my_flag") - .build(); - Assert.assertEquals(0, snapshot.dataPoints.size()); - } + @Test + public void testMinimal() { + StateSetSnapshot snapshot = + StateSetSnapshot.builder() + .name("my_flag") + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder().state("flag", true).build()) + .build(); + Assert.assertEquals(1, snapshot.dataPoints.size()); + } - @Test(expected = UnsupportedOperationException.class) - public void testDataImmutable() { - StateSetSnapshot.StateSetDataPointSnapshot data = StateSetSnapshot.StateSetDataPointSnapshot.builder() - .state("a", true) - .state("b", true) - .state("c", true) - .build(); - Iterator iterator = data.iterator(); - iterator.next(); - iterator.remove(); - } + @Test + public void testEmpty() { + StateSetSnapshot snapshot = StateSetSnapshot.builder().name("my_flag").build(); + Assert.assertEquals(0, snapshot.dataPoints.size()); + } - @Test(expected = IllegalArgumentException.class) - public void testDuplicateState() { - StateSetSnapshot.StateSetDataPointSnapshot data = StateSetSnapshot.StateSetDataPointSnapshot.builder() - .state("a", true) - .state("b", true) - .state("a", true) - .build(); - } + @Test(expected = UnsupportedOperationException.class) + public void testDataImmutable() { + StateSetSnapshot.StateSetDataPointSnapshot data = + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .state("a", true) + .state("b", true) + .state("c", true) + .build(); + Iterator iterator = data.iterator(); + iterator.next(); + iterator.remove(); + } - @Test(expected = UnsupportedOperationException.class) - public void testStateSetImmutable() { - StateSetSnapshot snapshot = StateSetSnapshot.builder() - .name("flags") - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(Labels.of("entity", "controller")) - .state("feature", true) - .build() - ) - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .labels(Labels.of("entity", "api")) - .state("feature", true) - .build() - ) - .build(); - Iterator iterator = snapshot.getDataPoints().iterator(); - iterator.next(); - iterator.remove(); - } + @Test(expected = IllegalArgumentException.class) + public void testDuplicateState() { + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .state("a", true) + .state("b", true) + .state("a", true) + .build(); + } - @Test(expected = IllegalArgumentException.class) - public void testLabelsUnique() { + @Test(expected = UnsupportedOperationException.class) + public void testStateSetImmutable() { + StateSetSnapshot snapshot = StateSetSnapshot.builder() - .name("flags") - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .state("feature", true) - .build() - ) - .dataPoint(StateSetSnapshot.StateSetDataPointSnapshot.builder() - .state("feature", true) - .build() - ) - .build(); - } + .name("flags") + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(Labels.of("entity", "controller")) + .state("feature", true) + .build()) + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder() + .labels(Labels.of("entity", "api")) + .state("feature", true) + .build()) + .build(); + Iterator iterator = + snapshot.getDataPoints().iterator(); + iterator.next(); + iterator.remove(); + } + + @Test(expected = IllegalArgumentException.class) + public void testLabelsUnique() { + StateSetSnapshot.builder() + .name("flags") + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder().state("feature", true).build()) + .dataPoint( + StateSetSnapshot.StateSetDataPointSnapshot.builder().state("feature", true).build()) + .build(); + } } diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java index f88458cc3..1f70f6520 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/UnitTest.java @@ -3,49 +3,42 @@ import org.junit.Assert; import org.junit.Test; -import static org.junit.Assert.fail; - public class UnitTest { - @Test - public void testEmpty() { - try { - new Unit(" "); - fail("Expected IllegalArgumentException"); - } catch (IllegalArgumentException e) { - // Expected - } - } - - @Test - public void testEquals1() { - Unit unit1 = Unit.BYTES; - Unit unit2 = new Unit("bytes"); - - Assert.assertEquals(unit2, unit1); - } - - @Test - public void testEquals2() { - Unit unit1 = new Unit("bytes "); - Unit unit2 = new Unit("bytes"); - - Assert.assertEquals(unit2, unit1); - } - - @Test - public void testEquals3() { - Unit unit1 = new Unit(" bytes"); - Unit unit2 = new Unit("bytes"); - - Assert.assertEquals(unit2, unit1); - } - - @Test - public void testEquals4() { - Unit unit1 = new Unit(" bytes "); - Unit unit2 = new Unit("bytes"); - - Assert.assertEquals(unit2, unit1); - } + @Test(expected = IllegalArgumentException.class) + public void testEmpty() { + new Unit(" "); + } + + @Test + public void testEquals1() { + Unit unit1 = Unit.BYTES; + Unit unit2 = new Unit("bytes"); + + Assert.assertEquals(unit2, unit1); + } + + @Test + public void testEquals2() { + Unit unit1 = new Unit("bytes "); + Unit unit2 = new Unit("bytes"); + + Assert.assertEquals(unit2, unit1); + } + + @Test + public void testEquals3() { + Unit unit1 = new Unit(" bytes"); + Unit unit2 = new Unit("bytes"); + + Assert.assertEquals(unit2, unit1); + } + + @Test + public void testEquals4() { + Unit unit1 = new Unit(" bytes "); + Unit unit2 = new Unit("bytes"); + + Assert.assertEquals(unit2, unit1); + } }