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 // Sort by prometheus name so snapshots that share a prometheus name (i.e. the same exposed 040 // family) are adjacent. TextFormatUtil.mergeDuplicates depends on this ordering to detect 041 // duplicates in a single linear pass; if this sort key changes, update that fast path too. 042 list.sort(comparing(s -> s.getMetadata().getPrometheusName())); 043 044 // Validate no conflicting metric types 045 for (int i = 0; i < list.size() - 1; i++) { 046 String name1 = list.get(i).getMetadata().getPrometheusName(); 047 String name2 = list.get(i + 1).getMetadata().getPrometheusName(); 048 049 if (name1.equals(name2)) { 050 MetricSnapshot s1 = list.get(i); 051 MetricSnapshot s2 = list.get(i + 1); 052 Class<?> type1 = s1.getClass(); 053 Class<?> type2 = s2.getClass(); 054 055 if (!type1.equals(type2)) { 056 throw new IllegalArgumentException( 057 name1 058 + ": conflicting metric types: " 059 + type1.getSimpleName() 060 + " and " 061 + type2.getSimpleName()); 062 } 063 064 // HistogramSnapshot: gauge histogram vs classic histogram are semantically different 065 if (s1 instanceof HistogramSnapshot) { 066 HistogramSnapshot h1 = (HistogramSnapshot) s1; 067 HistogramSnapshot h2 = (HistogramSnapshot) s2; 068 if (h1.isGaugeHistogram() != h2.isGaugeHistogram()) { 069 throw new IllegalArgumentException( 070 name1 + ": conflicting histogram types: gauge histogram and classic histogram"); 071 } 072 } 073 } 074 } 075 076 this.snapshots = unmodifiableList(list); 077 } 078 079 public static MetricSnapshots of(MetricSnapshot... snapshots) { 080 return new MetricSnapshots(snapshots); 081 } 082 083 @Override 084 public Iterator<MetricSnapshot> iterator() { 085 return snapshots.iterator(); 086 } 087 088 public int size() { 089 return snapshots.size(); 090 } 091 092 public MetricSnapshot get(int i) { 093 return snapshots.get(i); 094 } 095 096 public Stream<MetricSnapshot> stream() { 097 return snapshots.stream(); 098 } 099 100 public static Builder builder() { 101 return new Builder(); 102 } 103 104 public static class Builder { 105 106 private final List<MetricSnapshot> snapshots = new ArrayList<>(); 107 private final Set<String> prometheusNames = new HashSet<>(); 108 109 private Builder() {} 110 111 public boolean containsMetricName(String name) { 112 if (name == null) { 113 return false; 114 } 115 String prometheusName = prometheusName(name); 116 return prometheusNames.contains(prometheusName); 117 } 118 119 /** Add a metric snapshot. Call multiple times to add multiple metric snapshots. */ 120 public Builder metricSnapshot(MetricSnapshot snapshot) { 121 snapshots.add(snapshot); 122 prometheusNames.add(snapshot.getMetadata().getPrometheusName()); 123 return this; 124 } 125 126 public MetricSnapshots build() { 127 return new MetricSnapshots(snapshots); 128 } 129 } 130}