001package io.prometheus.metrics.model.snapshots;
002
003import static io.prometheus.metrics.model.snapshots.PrometheusNaming.prometheusName;
004import static java.util.Collections.unmodifiableList;
005import static java.util.Comparator.comparing;
006
007import java.util.ArrayList;
008import java.util.Arrays;
009import java.util.Collection;
010import java.util.HashSet;
011import java.util.Iterator;
012import java.util.List;
013import java.util.Set;
014import java.util.stream.Stream;
015
016/** Immutable list of metric snapshots. */
017public class MetricSnapshots implements Iterable<MetricSnapshot> {
018
019  private final List<MetricSnapshot> snapshots;
020
021  /** See {@link #MetricSnapshots(Collection)} */
022  public MetricSnapshots(MetricSnapshot... snapshots) {
023    this(Arrays.asList(snapshots));
024  }
025
026  /**
027   * To create MetricSnapshots, you can either call the constructor directly or use {@link
028   * #builder()}.
029   *
030   * @param snapshots the constructor creates a sorted copy of snapshots.
031   * @throws IllegalArgumentException if snapshots contain conflicting metric types (same name but
032   *     different metric types like Counter vs Gauge), or if two HistogramSnapshots share a name
033   *     but differ in gauge histogram vs classic histogram.
034   */
035  public MetricSnapshots(Collection<MetricSnapshot> snapshots) {
036    List<MetricSnapshot> list = new ArrayList<>(snapshots);
037    list.sort(comparing(s -> s.getMetadata().getPrometheusName()));
038
039    // Validate no conflicting metric types
040    for (int i = 0; i < list.size() - 1; i++) {
041      String name1 = list.get(i).getMetadata().getPrometheusName();
042      String name2 = list.get(i + 1).getMetadata().getPrometheusName();
043
044      if (name1.equals(name2)) {
045        MetricSnapshot s1 = list.get(i);
046        MetricSnapshot s2 = list.get(i + 1);
047        Class<?> type1 = s1.getClass();
048        Class<?> type2 = s2.getClass();
049
050        if (!type1.equals(type2)) {
051          throw new IllegalArgumentException(
052              name1
053                  + ": conflicting metric types: "
054                  + type1.getSimpleName()
055                  + " and "
056                  + type2.getSimpleName());
057        }
058
059        // HistogramSnapshot: gauge histogram vs classic histogram are semantically different
060        if (s1 instanceof HistogramSnapshot) {
061          HistogramSnapshot h1 = (HistogramSnapshot) s1;
062          HistogramSnapshot h2 = (HistogramSnapshot) s2;
063          if (h1.isGaugeHistogram() != h2.isGaugeHistogram()) {
064            throw new IllegalArgumentException(
065                name1 + ": conflicting histogram types: gauge histogram and classic histogram");
066          }
067        }
068      }
069    }
070
071    this.snapshots = unmodifiableList(list);
072  }
073
074  public static MetricSnapshots of(MetricSnapshot... snapshots) {
075    return new MetricSnapshots(snapshots);
076  }
077
078  @Override
079  public Iterator<MetricSnapshot> iterator() {
080    return snapshots.iterator();
081  }
082
083  public int size() {
084    return snapshots.size();
085  }
086
087  public MetricSnapshot get(int i) {
088    return snapshots.get(i);
089  }
090
091  public Stream<MetricSnapshot> stream() {
092    return snapshots.stream();
093  }
094
095  public static Builder builder() {
096    return new Builder();
097  }
098
099  public static class Builder {
100
101    private final List<MetricSnapshot> snapshots = new ArrayList<>();
102    private final Set<String> prometheusNames = new HashSet<>();
103
104    private Builder() {}
105
106    public boolean containsMetricName(String name) {
107      if (name == null) {
108        return false;
109      }
110      String prometheusName = prometheusName(name);
111      return prometheusNames.contains(prometheusName);
112    }
113
114    /** Add a metric snapshot. Call multiple times to add multiple metric snapshots. */
115    public Builder metricSnapshot(MetricSnapshot snapshot) {
116      snapshots.add(snapshot);
117      prometheusNames.add(snapshot.getMetadata().getPrometheusName());
118      return this;
119    }
120
121    public MetricSnapshots build() {
122      return new MetricSnapshots(snapshots);
123    }
124  }
125}