001package io.prometheus.metrics.model.snapshots; 002 003import io.prometheus.metrics.annotations.StableApi; 004import io.prometheus.metrics.config.EscapingScheme; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.Collections; 008import java.util.Comparator; 009import java.util.List; 010import javax.annotation.Nullable; 011 012/** Base class for metric snapshots. */ 013@StableApi 014public abstract class MetricSnapshot { 015 private final MetricMetadata metadata; 016 protected final List<? extends DataPointSnapshot> dataPoints; 017 018 protected MetricSnapshot( 019 MetricMetadata metadata, 020 Collection<? extends DataPointSnapshot> dataPoints, 021 boolean internal) { 022 this.metadata = metadata; 023 if (internal) { 024 this.dataPoints = (List<? extends DataPointSnapshot>) dataPoints; 025 } else { 026 if (metadata == null) { 027 throw new NullPointerException("metadata"); 028 } 029 if (dataPoints == null) { 030 throw new NullPointerException("dataPoints"); 031 } 032 List<? extends DataPointSnapshot> dataCopy = new ArrayList<>(dataPoints); 033 dataCopy.sort(Comparator.comparing(DataPointSnapshot::getLabels)); 034 this.dataPoints = Collections.unmodifiableList(dataCopy); 035 validateLabels(this.dataPoints, metadata); 036 } 037 } 038 039 public MetricMetadata getMetadata() { 040 return metadata; 041 } 042 043 public abstract List<? extends DataPointSnapshot> getDataPoints(); 044 045 private static <T extends DataPointSnapshot> void validateLabels( 046 List<T> dataPoints, MetricMetadata metadata) { 047 // Verify that labels are unique (the same set of names/values must not be used multiple times 048 // for the same metric). 049 for (int i = 0; i < dataPoints.size() - 1; i++) { 050 if (dataPoints.get(i).getLabels().equals(dataPoints.get(i + 1).getLabels())) { 051 throw new DuplicateLabelsException(metadata, dataPoints.get(i).getLabels()); 052 } 053 } 054 // Should we verify that all entries in data have the same label names? 055 // No. They should have the same label names, but according to OpenMetrics this is not a MUST. 056 } 057 058 public abstract static class Builder<T extends Builder<T>> { 059 060 @Nullable protected String name; 061 @Nullable protected String help; 062 @Nullable protected Unit unit; 063 064 /** 065 * The name is required. If the name is missing or invalid, {@code build()} will throw an {@link 066 * IllegalArgumentException}. See {@link PrometheusNaming#isValidMetricName(String)} for info on 067 * valid metric names. 068 */ 069 public T name(String name) { 070 this.name = name; 071 return self(); 072 } 073 074 public T help(@Nullable String help) { 075 this.help = help; 076 return self(); 077 } 078 079 public T unit(@Nullable Unit unit) { 080 this.unit = unit; 081 return self(); 082 } 083 084 public abstract MetricSnapshot build(); 085 086 protected MetricMetadata buildMetadata() { 087 if (name == null) { 088 throw new IllegalArgumentException("Missing required field: name is null"); 089 } 090 return MetricMetadataSupport.metricMetadata(name, help, unit); 091 } 092 093 protected abstract T self(); 094 } 095 096 abstract MetricSnapshot escape( 097 EscapingScheme escapingScheme, List<? extends DataPointSnapshot> dataPointSnapshots); 098}