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 @Override 084 public CounterSnapshot collect() { 085 return (CounterSnapshot) super.collect(); 086 } 087 088 @Override 089 protected boolean isExemplarsEnabled() { 090 return exemplarsEnabled; 091 } 092 093 @Override 094 protected DataPoint newDataPoint() { 095 if (isExemplarsEnabled()) { 096 return new DataPoint(new ExemplarSampler(exemplarSamplerConfig)); 097 } else { 098 return new DataPoint(null); 099 } 100 } 101 102 @Override 103 protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricData) { 104 List<CounterSnapshot.CounterDataPointSnapshot> data = new ArrayList<>(labels.size()); 105 for (int i = 0; i < labels.size(); i++) { 106 data.add(metricData.get(i).collect(labels.get(i))); 107 } 108 return new CounterSnapshot(getMetadata(), data); 109 } 110 111 static String stripTotalSuffix(String name) { 112 if (name != null && (name.endsWith("_total") || name.endsWith(".total"))) { 113 name = name.substring(0, name.length() - 6); 114 } 115 return name; 116 } 117 118 class DataPoint implements CounterDataPoint { 119 120 private final DoubleAdder doubleValue = new DoubleAdder(); 121 // LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations, 122 // and DoubleAdder for double observations. If the user doesn't observe any double at all, 123 // we will be using the LongAdder and get the best performance. 124 private final LongAdder longValue = new LongAdder(); 125 private final long createdTimeMillis = System.currentTimeMillis(); 126 private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false 127 128 private DataPoint(ExemplarSampler exemplarSampler) { 129 this.exemplarSampler = exemplarSampler; 130 } 131 132 /** 133 * {@inheritDoc} 134 */ 135 @Override 136 public void inc(long amount) { 137 validateAndAdd(amount); 138 if (isExemplarsEnabled()) { 139 exemplarSampler.observe(amount); 140 } 141 } 142 143 /** 144 * {@inheritDoc} 145 */ 146 @Override 147 public void inc(double amount) { 148 validateAndAdd(amount); 149 if (isExemplarsEnabled()) { 150 exemplarSampler.observe(amount); 151 } 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 @Override 158 public void incWithExemplar(long amount, Labels labels) { 159 validateAndAdd(amount); 160 if (isExemplarsEnabled()) { 161 exemplarSampler.observeWithExemplar(amount, labels); 162 } 163 } 164 165 /** 166 * {@inheritDoc} 167 */ 168 @Override 169 public void incWithExemplar(double amount, Labels labels) { 170 validateAndAdd(amount); 171 if (isExemplarsEnabled()) { 172 exemplarSampler.observeWithExemplar(amount, labels); 173 } 174 } 175 176 private void validateAndAdd(long amount) { 177 if (amount < 0) { 178 throw new IllegalArgumentException("Negative increment " + amount + " is illegal for Counter metrics."); 179 } 180 longValue.add(amount); 181 } 182 183 private void validateAndAdd(double amount) { 184 if (amount < 0) { 185 throw new IllegalArgumentException("Negative increment " + amount + " is illegal for Counter metrics."); 186 } 187 doubleValue.add(amount); 188 } 189 190 private CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) { 191 // Read the exemplar first. Otherwise, there is a race condition where you might 192 // see an Exemplar for a value that's not counted yet. 193 // If there are multiple Exemplars (by default it's just one), use the newest. 194 Exemplar latestExemplar = null; 195 if (exemplarSampler != null) { 196 for (Exemplar exemplar : exemplarSampler.collect()) { 197 if (latestExemplar == null || exemplar.getTimestampMillis() > latestExemplar.getTimestampMillis()) { 198 latestExemplar = exemplar; 199 } 200 } 201 } 202 return new CounterSnapshot.CounterDataPointSnapshot(longValue.sum() + doubleValue.sum(), labels, latestExemplar, createdTimeMillis); 203 } 204 } 205 206 public static Builder builder() { 207 return new Builder(PrometheusProperties.get()); 208 } 209 210 public static Builder builder(PrometheusProperties config) { 211 return new Builder(config); 212 } 213 214 public static class Builder extends StatefulMetric.Builder<Builder, Counter> { 215 216 private Builder(PrometheusProperties properties) { 217 super(Collections.emptyList(), properties); 218 } 219 220 /** 221 * The {@code _total} suffix will automatically be appended if it's missing. 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 * In the example above both {@code c1} and {@code c2} would be named {@code "events_total"} in Prometheus. 231 * <p> 232 * Throws an {@link IllegalArgumentException} if 233 * {@link io.prometheus.metrics.model.snapshots.PrometheusNaming#isValidMetricName(String) MetricMetadata.isValidMetricName(name)} 234 * is {@code false}. 235 */ 236 @Override 237 public Builder name(String name) { 238 return super.name(stripTotalSuffix(name)); 239 } 240 241 @Override 242 public Counter build() { 243 return new Counter(this, properties); 244 } 245 246 @Override 247 protected Builder self() { 248 return this; 249 } 250 } 251}