001package io.prometheus.metrics.core.metrics; 002 003import io.prometheus.metrics.annotations.StableApi; 004import io.prometheus.metrics.config.PrometheusProperties; 005import io.prometheus.metrics.model.registry.MetricType; 006import io.prometheus.metrics.model.snapshots.Label; 007import io.prometheus.metrics.model.snapshots.Labels; 008import io.prometheus.metrics.model.snapshots.MetricFamilyDescriptor; 009import io.prometheus.metrics.model.snapshots.MetricMetadata; 010import io.prometheus.metrics.model.snapshots.PrometheusNaming; 011import io.prometheus.metrics.model.snapshots.Unit; 012import java.util.Arrays; 013import java.util.HashSet; 014import java.util.List; 015import java.util.Set; 016import javax.annotation.Nullable; 017 018/** 019 * Almost all metrics have fixed metadata, i.e. the metric name is known when the metric is created. 020 * 021 * <p>An exception would be a metric that is a bridge to a 3rd party metric library, where the 022 * metric name has to be retrieved from the 3rd party metric library at scrape time. 023 */ 024@StableApi 025public abstract class MetricWithFixedMetadata extends Metric { 026 027 protected final MetricMetadata metadata; 028 protected final String[] labelNames; 029 030 protected MetricWithFixedMetadata(Builder<?, ?> builder) { 031 super(builder); 032 String name = makeName(builder.name, builder.unit); 033 if (builder.originalName == null) { 034 throw new IllegalArgumentException("Missing required field: name is null"); 035 } 036 String originalName = builder.originalName; 037 String expositionBaseName = makeExpositionBaseName(originalName, builder.unit); 038 this.metadata = 039 MetricMetadata.builder() 040 .name(name) 041 .expositionBaseName(expositionBaseName) 042 .originalName(originalName) 043 .help(builder.help) 044 .unit(builder.unit) 045 .build(); 046 this.labelNames = Arrays.copyOf(builder.labelNames, builder.labelNames.length); 047 } 048 049 @Override 050 @Nullable 051 @SuppressWarnings("deprecation") 052 public MetricFamilyDescriptor getMetricFamilyDescriptor() { 053 MetricType metricType = getMetricType(); 054 if (metricType == null) { 055 return null; 056 } 057 return MetricFamilyDescriptor.of(metricType, metadata, getPrometheusLabels()); 058 } 059 060 /** 061 * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. 062 */ 063 @Override 064 @Deprecated 065 @SuppressWarnings("InlineMeSuggester") 066 public MetricMetadata getMetadata() { 067 return metadata; 068 } 069 070 private String makeName(@Nullable String name, @Nullable Unit unit) { 071 if (name == null) { 072 throw new IllegalArgumentException("Missing required field: name is null"); 073 } 074 if (unit != null) { 075 if (!name.endsWith("_" + unit) && !name.endsWith("." + unit)) { 076 name += "_" + unit; 077 } 078 } 079 return name; 080 } 081 082 private String makeExpositionBaseName(@Nullable String expositionBaseName, @Nullable Unit unit) { 083 if (expositionBaseName == null) { 084 throw new IllegalArgumentException("Missing required field: name is null"); 085 } 086 if (unit != null) { 087 if (!expositionBaseName.endsWith("_" + unit) && !expositionBaseName.endsWith("." + unit)) { 088 expositionBaseName += "_" + unit; 089 } 090 } 091 return expositionBaseName; 092 } 093 094 /** 095 * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. 096 */ 097 @Override 098 @Deprecated 099 @SuppressWarnings("InlineMeSuggester") 100 public String getPrometheusName() { 101 return metadata.getPrometheusName(); 102 } 103 104 /** 105 * @deprecated Use {@link #getMetricFamilyDescriptor()} instead. 106 */ 107 @Override 108 @Deprecated 109 @SuppressWarnings("InlineMeSuggester") 110 public Set<String> getLabelNames() { 111 return getPrometheusLabels(); 112 } 113 114 private Set<String> getPrometheusLabels() { 115 Set<String> names = new HashSet<>(); 116 for (String labelName : labelNames) { 117 names.add(PrometheusNaming.prometheusName(labelName)); 118 } 119 for (Label label : constLabels) { 120 names.add(PrometheusNaming.prometheusName(label.getName())); 121 } 122 return names; 123 } 124 125 public abstract static class Builder<B extends Builder<B, M>, M extends MetricWithFixedMetadata> 126 extends Metric.Builder<B, M> { 127 128 @Nullable private String name; 129 @Nullable private String originalName; 130 @Nullable private Unit unit; 131 @Nullable private String help; 132 private String[] labelNames = new String[0]; 133 134 protected Builder(List<String> illegalLabelNames, PrometheusProperties properties) { 135 super(illegalLabelNames, properties); 136 } 137 138 public B name(String name) { 139 String error = PrometheusNaming.validateMetricName(name); 140 if (error != null) { 141 throw new IllegalArgumentException("'" + name + "': Illegal metric name: " + error); 142 } 143 this.name = name; 144 this.originalName = name; 145 return self(); 146 } 147 148 /** 149 * Set the metric name and original name separately. Used by Counter and Info builders which 150 * strip type suffixes from the name but preserve the original for exposition. 151 */ 152 protected B nameWithOriginal(String name, String originalName) { 153 String error = PrometheusNaming.validateMetricName(name); 154 if (error != null) { 155 throw new IllegalArgumentException("'" + name + "': Illegal metric name: " + error); 156 } 157 error = PrometheusNaming.validateMetricName(originalName); 158 if (error != null) { 159 throw new IllegalArgumentException("'" + originalName + "': Illegal metric name: " + error); 160 } 161 this.name = name; 162 this.originalName = originalName; 163 return self(); 164 } 165 166 public B unit(@Nullable Unit unit) { 167 this.unit = unit; 168 return self(); 169 } 170 171 public B help(String help) { 172 this.help = help; 173 return self(); 174 } 175 176 public B labelNames(String... labelNames) { 177 for (String labelName : labelNames) { 178 if (!PrometheusNaming.isValidLabelName(labelName)) { 179 throw new IllegalArgumentException(labelName + ": illegal label name"); 180 } 181 if (illegalLabelNames.contains(labelName)) { 182 throw new IllegalArgumentException( 183 labelName + ": illegal label name for this metric type"); 184 } 185 if (constLabels.contains(labelName)) { 186 throw new IllegalArgumentException(labelName + ": duplicate label name"); 187 } 188 } 189 this.labelNames = labelNames; 190 return self(); 191 } 192 193 @Override 194 public B constLabels(Labels constLabels) { 195 for (String labelName : labelNames) { 196 if (constLabels.contains(labelName)) { // Labels.contains() treats dots like underscores 197 throw new IllegalArgumentException(labelName + ": duplicate label name"); 198 } 199 } 200 return super.constLabels(constLabels); 201 } 202 203 @Override 204 public abstract M build(); 205 206 @Override 207 protected abstract B self(); 208 } 209}