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}