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}