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