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