001package io.prometheus.metrics.model.snapshots;
002
003import io.prometheus.metrics.annotations.StableApi;
004import io.prometheus.metrics.config.EscapingScheme;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Comparator;
009import java.util.List;
010import javax.annotation.Nullable;
011
012/** Base class for metric snapshots. */
013@StableApi
014public abstract class MetricSnapshot {
015  private final MetricMetadata metadata;
016  protected final List<? extends DataPointSnapshot> dataPoints;
017
018  protected MetricSnapshot(
019      MetricMetadata metadata,
020      Collection<? extends DataPointSnapshot> dataPoints,
021      boolean internal) {
022    this.metadata = metadata;
023    if (internal) {
024      this.dataPoints = (List<? extends DataPointSnapshot>) dataPoints;
025    } else {
026      if (metadata == null) {
027        throw new NullPointerException("metadata");
028      }
029      if (dataPoints == null) {
030        throw new NullPointerException("dataPoints");
031      }
032      List<? extends DataPointSnapshot> dataCopy = new ArrayList<>(dataPoints);
033      dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels));
034      this.dataPoints = Collections.unmodifiableList(dataCopy);
035      validateLabels(this.dataPoints, metadata);
036    }
037  }
038
039  public MetricMetadata getMetadata() {
040    return metadata;
041  }
042
043  public abstract List<? extends DataPointSnapshot> getDataPoints();
044
045  private static <T extends DataPointSnapshot> void validateLabels(
046      List<T> dataPoints, MetricMetadata metadata) {
047    // Verify that labels are unique (the same set of names/values must not be used multiple times
048    // for the same metric).
049    for (int i = 0; i < dataPoints.size() - 1; i++) {
050      if (dataPoints.get(i).getLabels().equals(dataPoints.get(i + 1).getLabels())) {
051        throw new DuplicateLabelsException(metadata, dataPoints.get(i).getLabels());
052      }
053    }
054    // Should we verify that all entries in data have the same label names?
055    // No. They should have the same label names, but according to OpenMetrics this is not a MUST.
056  }
057
058  public abstract static class Builder<T extends Builder<T>> {
059
060    @Nullable protected String name;
061    @Nullable protected String help;
062    @Nullable protected Unit unit;
063
064    /**
065     * The name is required. If the name is missing or invalid, {@code build()} will throw an {@link
066     * IllegalArgumentException}. See {@link PrometheusNaming#isValidMetricName(String)} for info on
067     * valid metric names.
068     */
069    public T name(String name) {
070      this.name = name;
071      return self();
072    }
073
074    public T help(@Nullable String help) {
075      this.help = help;
076      return self();
077    }
078
079    public T unit(@Nullable Unit unit) {
080      this.unit = unit;
081      return self();
082    }
083
084    public abstract MetricSnapshot build();
085
086    protected MetricMetadata buildMetadata() {
087      if (name == null) {
088        throw new IllegalArgumentException("Missing required field: name is null");
089      }
090      return MetricMetadataSupport.metricMetadata(name, help, unit);
091    }
092
093    protected abstract T self();
094  }
095
096  abstract MetricSnapshot escape(
097      EscapingScheme escapingScheme, List<? extends DataPointSnapshot> dataPointSnapshots);
098}