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