001package io.prometheus.metrics.core.metrics; 002 003import io.prometheus.metrics.config.MetricsProperties; 004import io.prometheus.metrics.config.PrometheusProperties; 005import io.prometheus.metrics.core.datapoints.CounterDataPoint; 006import io.prometheus.metrics.core.exemplars.ExemplarSampler; 007import io.prometheus.metrics.core.exemplars.ExemplarSamplerConfig; 008import io.prometheus.metrics.model.snapshots.CounterSnapshot; 009import io.prometheus.metrics.model.snapshots.Exemplar; 010import io.prometheus.metrics.model.snapshots.Labels; 011import java.util.ArrayList; 012import java.util.Collections; 013import java.util.List; 014import java.util.concurrent.atomic.DoubleAdder; 015import java.util.concurrent.atomic.LongAdder; 016 017/** 018 * Counter metric. 019 * 020 * <p>Example usage: 021 * 022 * <pre>{@code 023 * Counter requestCount = Counter.builder() 024 * .name("requests_total") 025 * .help("Total number of requests") 026 * .labelNames("path", "status") 027 * .register(); 028 * requestCount.labelValues("/hello-world", "200").inc(); 029 * requestCount.labelValues("/hello-world", "500").inc(); 030 * }</pre> 031 */ 032public class Counter extends StatefulMetric<CounterDataPoint, Counter.DataPoint> 033 implements CounterDataPoint { 034 035 private final boolean exemplarsEnabled; 036 private final ExemplarSamplerConfig exemplarSamplerConfig; 037 038 private Counter(Builder builder, PrometheusProperties prometheusProperties) { 039 super(builder); 040 MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties); 041 exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled); 042 if (exemplarsEnabled) { 043 exemplarSamplerConfig = 044 new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1); 045 } else { 046 exemplarSamplerConfig = null; 047 } 048 } 049 050 @Override 051 public void inc(long amount) { 052 getNoLabels().inc(amount); 053 } 054 055 @Override 056 public void inc(double amount) { 057 getNoLabels().inc(amount); 058 } 059 060 @Override 061 public void incWithExemplar(long amount, Labels labels) { 062 getNoLabels().incWithExemplar(amount, labels); 063 } 064 065 @Override 066 public void incWithExemplar(double amount, Labels labels) { 067 getNoLabels().incWithExemplar(amount, labels); 068 } 069 070 @Override 071 public double get() { 072 return getNoLabels().get(); 073 } 074 075 @Override 076 public long getLongValue() { 077 return getNoLabels().getLongValue(); 078 } 079 080 @Override 081 public CounterSnapshot collect() { 082 return (CounterSnapshot) super.collect(); 083 } 084 085 @Override 086 protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricData) { 087 List<CounterSnapshot.CounterDataPointSnapshot> data = new ArrayList<>(labels.size()); 088 for (int i = 0; i < labels.size(); i++) { 089 data.add(metricData.get(i).collect(labels.get(i))); 090 } 091 return new CounterSnapshot(getMetadata(), data); 092 } 093 094 @Override 095 protected boolean isExemplarsEnabled() { 096 return exemplarsEnabled; 097 } 098 099 @Override 100 protected DataPoint newDataPoint() { 101 if (isExemplarsEnabled()) { 102 return new DataPoint(new ExemplarSampler(exemplarSamplerConfig)); 103 } else { 104 return new DataPoint(null); 105 } 106 } 107 108 static String stripTotalSuffix(String name) { 109 if (name != null && (name.endsWith("_total") || name.endsWith(".total"))) { 110 name = name.substring(0, name.length() - 6); 111 } 112 return name; 113 } 114 115 class DataPoint implements CounterDataPoint { 116 117 private final DoubleAdder doubleValue = new DoubleAdder(); 118 // LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations, 119 // and DoubleAdder for double observations. If the user doesn't observe any double at all, 120 // we will be using the LongAdder and get the best performance. 121 private final LongAdder longValue = new LongAdder(); 122 private final long createdTimeMillis = System.currentTimeMillis(); 123 private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false 124 125 private DataPoint(ExemplarSampler exemplarSampler) { 126 this.exemplarSampler = exemplarSampler; 127 } 128 129 @Override 130 public double get() { 131 return longValue.sum() + doubleValue.sum(); 132 } 133 134 @Override 135 public long getLongValue() { 136 return longValue.sum() + (long) doubleValue.sum(); 137 } 138 139 @Override 140 public void inc(long amount) { 141 validateAndAdd(amount); 142 if (isExemplarsEnabled()) { 143 exemplarSampler.observe((double) amount); 144 } 145 } 146 147 @Override 148 public void inc(double amount) { 149 validateAndAdd(amount); 150 if (isExemplarsEnabled()) { 151 exemplarSampler.observe(amount); 152 } 153 } 154 155 @Override 156 public void incWithExemplar(long amount, Labels labels) { 157 validateAndAdd(amount); 158 if (isExemplarsEnabled()) { 159 exemplarSampler.observeWithExemplar((double) amount, labels); 160 } 161 } 162 163 @Override 164 public void incWithExemplar(double amount, Labels labels) { 165 validateAndAdd(amount); 166 if (isExemplarsEnabled()) { 167 exemplarSampler.observeWithExemplar(amount, labels); 168 } 169 } 170 171 private void validateAndAdd(long amount) { 172 if (amount < 0) { 173 throw new IllegalArgumentException( 174 "Negative increment " + amount + " is illegal for Counter metrics."); 175 } 176 longValue.add(amount); 177 } 178 179 private void validateAndAdd(double amount) { 180 if (amount < 0) { 181 throw new IllegalArgumentException( 182 "Negative increment " + amount + " is illegal for Counter metrics."); 183 } 184 doubleValue.add(amount); 185 } 186 187 private CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) { 188 // Read the exemplar first. Otherwise, there is a race condition where you might 189 // see an Exemplar for a value that's not counted yet. 190 // If there are multiple Exemplars (by default it's just one), use the newest. 191 Exemplar latestExemplar = null; 192 if (exemplarSampler != null) { 193 for (Exemplar exemplar : exemplarSampler.collect()) { 194 if (latestExemplar == null 195 || exemplar.getTimestampMillis() > latestExemplar.getTimestampMillis()) { 196 latestExemplar = exemplar; 197 } 198 } 199 } 200 return new CounterSnapshot.CounterDataPointSnapshot( 201 get(), labels, latestExemplar, createdTimeMillis); 202 } 203 } 204 205 public static Builder builder() { 206 return new Builder(PrometheusProperties.get()); 207 } 208 209 public static Builder builder(PrometheusProperties config) { 210 return new Builder(config); 211 } 212 213 public static class Builder extends StatefulMetric.Builder<Builder, Counter> { 214 215 private Builder(PrometheusProperties properties) { 216 super(Collections.emptyList(), properties); 217 } 218 219 /** 220 * The {@code _total} suffix will automatically be appended if it's missing. 221 * 222 * <pre>{@code 223 * Counter c1 = Counter.builder() 224 * .name("events_total") 225 * .build(); 226 * Counter c2 = Counter.builder() 227 * .name("events") 228 * .build(); 229 * }</pre> 230 * 231 * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in 232 * Prometheus. 233 * 234 * <p>Throws an {@link IllegalArgumentException} if {@link 235 * io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) 236 * MetricMetadata.isValidMetricName(name)} is {@code false}. 237 */ 238 @Override 239 public Builder name(String name) { 240 return super.name(stripTotalSuffix(name)); 241 } 242 243 @Override 244 public Counter build() { 245 return new Counter(this, properties); 246 } 247 248 @Override 249 protected Builder self() { 250 return this; 251 } 252 } 253}