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