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