001package io.prometheus.client; 002 003import io.prometheus.client.exemplars.CounterExemplarSampler; 004import io.prometheus.client.exemplars.Exemplar; 005import io.prometheus.client.exemplars.ExemplarConfig; 006 007import java.util.ArrayList; 008import java.util.Collections; 009import java.util.List; 010import java.util.Map; 011import java.util.concurrent.atomic.AtomicReference; 012 013import static java.lang.Boolean.FALSE; 014import static java.lang.Boolean.TRUE; 015 016/** 017 * Counter metric, to track counts of events or running totals. 018 * <p> 019 * Example of Counters include: 020 * <ul> 021 * <li>Number of requests processed</li> 022 * <li>Number of items that were inserted into a queue</li> 023 * <li>Total amount of data a system has processed</li> 024 * </ul> 025 * 026 * Counters can only go up (and be reset), if your use case can go down you should use a {@link Gauge} instead. 027 * Use the <code>rate()</code> function in Prometheus to calculate the rate of increase of a Counter. 028 * By convention, the names of Counters are suffixed by <code>_total</code>. 029 * 030 * <p> 031 * An example Counter: 032 * <pre> 033 * {@code 034 * class YourClass { 035 * static final Counter requests = Counter.build() 036 * .name("requests_total").help("Total requests.").register(); 037 * static final Counter failedRequests = Counter.build() 038 * .name("requests_failed_total").help("Total failed requests.").register(); 039 * 040 * void processRequest() { 041 * requests.inc(); 042 * try { 043 * // Your code here. 044 * } catch (Exception e) { 045 * failedRequests.inc(); 046 * throw e; 047 * } 048 * } 049 * } 050 * } 051 * </pre> 052 * 053 * <p> 054 * You can also use labels to track different types of metric: 055 * <pre> 056 * {@code 057 * class YourClass { 058 * static final Counter requests = Counter.build() 059 * .name("requests_total").help("Total requests.") 060 * .labelNames("method").register(); 061 * 062 * void processGetRequest() { 063 * requests.labels("get").inc(); 064 * // Your code here. 065 * } 066 * void processPostRequest() { 067 * requests.labels("post").inc(); 068 * // Your code here. 069 * } 070 * } 071 * } 072 * </pre> 073 * These can be aggregated and processed together much more easily in the Prometheus 074 * server than individual metrics for each labelset. 075 * 076 * If there is a suffix of <code>_total</code> on the metric name, it will be 077 * removed. When exposing the time series for counter value, a 078 * <code>_total</code> suffix will be added. This is for compatibility between 079 * OpenMetrics and the Prometheus text format, as OpenMetrics requires the 080 * <code>_total</code> suffix. 081 */ 082public class Counter extends SimpleCollector<Counter.Child> implements Collector.Describable { 083 084 private final Boolean exemplarsEnabled; // null means default from ExemplarConfig applies 085 private final CounterExemplarSampler exemplarSampler; 086 087 Counter(Builder b) { 088 super(b); 089 this.exemplarsEnabled = b.exemplarsEnabled; 090 this.exemplarSampler = b.exemplarSampler; 091 initializeNoLabelsChild(); 092 } 093 094 public static class Builder extends SimpleCollector.Builder<Builder, Counter> { 095 096 private Boolean exemplarsEnabled = null; 097 private CounterExemplarSampler exemplarSampler = null; 098 099 @Override 100 public Counter create() { 101 // Gracefully handle pre-OpenMetrics counters. 102 if (name.endsWith("_total")) { 103 name = name.substring(0, name.length() - 6); 104 } 105 dontInitializeNoLabelsChild = true; 106 return new Counter(this); 107 } 108 109 /** 110 * Enable exemplars and provide a custom {@link CounterExemplarSampler}. 111 */ 112 public Builder withExemplarSampler(CounterExemplarSampler exemplarSampler) { 113 if (exemplarSampler == null) { 114 throw new NullPointerException(); 115 } 116 this.exemplarSampler = exemplarSampler; 117 return withExemplars(); 118 } 119 120 /** 121 * Allow this counter to load exemplars from a {@link CounterExemplarSampler}. 122 * <p> 123 * If a specific exemplar sampler is configured for this counter that exemplar sampler is used 124 * (see {@link #withExemplarSampler(CounterExemplarSampler)}). 125 * Otherwise the default from {@link ExemplarConfig} is used. 126 */ 127 public Builder withExemplars() { 128 this.exemplarsEnabled = TRUE; 129 return this; 130 } 131 132 /** 133 * Prevent this counter from loading exemplars from a {@link CounterExemplarSampler}. 134 * <p> 135 * You can still provide exemplars for explicitly individual observations, e.g. using 136 * {@link #incWithExemplar(double, String...)}. 137 */ 138 public Builder withoutExemplars() { 139 this.exemplarsEnabled = FALSE; 140 return this; 141 } 142 } 143 144 /** 145 * Return a Builder to allow configuration of a new Counter. Ensures required fields are provided. 146 * 147 * @param name The name of the metric 148 * @param help The help string of the metric 149 */ 150 public static Builder build(String name, String help) { 151 return new Builder().name(name).help(help); 152 } 153 154 /** 155 * Return a Builder to allow configuration of a new Counter. 156 */ 157 public static Builder build() { 158 return new Builder(); 159 } 160 161 @Override 162 protected Child newChild() { 163 return new Child(exemplarsEnabled, exemplarSampler); 164 } 165 166 /** 167 * The value of a single Counter. 168 * <p> 169 * <em>Warning:</em> References to a Child become invalid after using 170 * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}, 171 */ 172 public static class Child { 173 private final DoubleAdder value = new DoubleAdder(); 174 private final long created = System.currentTimeMillis(); 175 private final Boolean exemplarsEnabled; 176 private final CounterExemplarSampler exemplarSampler; 177 private final AtomicReference<Exemplar> exemplar = new AtomicReference<Exemplar>(); 178 179 public Child(Boolean exemplarsEnabled, CounterExemplarSampler exemplarSampler) { 180 this.exemplarsEnabled = exemplarsEnabled; 181 this.exemplarSampler = exemplarSampler; 182 } 183 184 /** 185 * Increment the counter by 1. 186 */ 187 public void inc() { 188 inc(1); 189 } 190 191 /** 192 * Same as {@link #incWithExemplar(double, String...) incWithExemplar(1, exemplarLabels)}. 193 */ 194 public void incWithExemplar(String... exemplarLabels) { 195 incWithExemplar(1, exemplarLabels); 196 } 197 198 /** 199 * Same as {@link #incWithExemplar(double, Map) incWithExemplar(1, exemplarLabels)}. 200 */ 201 public void incWithExemplar(Map<String, String> exemplarLabels) { 202 incWithExemplar(1, exemplarLabels); 203 } 204 205 /** 206 * Increment the counter by the given amount. 207 * 208 * @throws IllegalArgumentException If amt is negative. 209 */ 210 public void inc(double amt) { 211 incWithExemplar(amt, (String[]) null); 212 } 213 214 /** 215 * Like {@link #inc(double)}, but additionally creates an exemplar. 216 * <p> 217 * This exemplar takes precedence over any exemplar returned by the {@link CounterExemplarSampler} configured 218 * in {@link ExemplarConfig}. 219 * <p> 220 * The exemplar will have {@code amt} as the value, {@code System.currentTimeMillis()} as the timestamp, 221 * and the specified labels. 222 * 223 * @param amt same as in {@link #inc(double)} 224 * @param exemplarLabels list of name/value pairs, as documented in {@link Exemplar#Exemplar(double, String...)}. 225 * A commonly used name is {@code "trace_id"}. 226 * Calling {@code incWithExemplar(amt)} means that an exemplar without labels will be created. 227 * Calling {@code incWithExemplar(amt, (String[]) null)} is equivalent 228 * to calling {@code inc(amt)}. 229 */ 230 public void incWithExemplar(double amt, String... exemplarLabels) { 231 Exemplar exemplar = exemplarLabels == null ? null : new Exemplar(amt, System.currentTimeMillis(), exemplarLabels); 232 if (amt < 0) { 233 throw new IllegalArgumentException("Amount to increment must be non-negative."); 234 } 235 value.add(amt); 236 updateExemplar(amt, exemplar); 237 } 238 239 /** 240 * Same as {@link #incWithExemplar(double, String...)}, but the exemplar labels are passed as a {@link Map}. 241 */ 242 public void incWithExemplar(double amt, Map<String, String> exemplarLabels) { 243 incWithExemplar(amt, Exemplar.mapToArray(exemplarLabels)); 244 } 245 246 private void updateExemplar(double amt, Exemplar userProvidedExemplar) { 247 Exemplar prev, next; 248 do { 249 prev = exemplar.get(); 250 if (userProvidedExemplar == null) { 251 next = sampleNextExemplar(amt, prev); 252 } else { 253 next = userProvidedExemplar; 254 } 255 if (next == null || next == prev) { 256 return; 257 } 258 } while (!exemplar.compareAndSet(prev, next)); 259 } 260 261 private Exemplar sampleNextExemplar(double amt, Exemplar prev) { 262 if (FALSE.equals(exemplarsEnabled)) { 263 return null; 264 } 265 if (exemplarSampler != null) { 266 return exemplarSampler.sample(amt, prev); 267 } 268 if (TRUE.equals(exemplarsEnabled) || ExemplarConfig.isExemplarsEnabled()) { 269 CounterExemplarSampler exemplarSampler = ExemplarConfig.getCounterExemplarSampler(); 270 if (exemplarSampler != null) { 271 return exemplarSampler.sample(amt, prev); 272 } 273 } 274 return null; 275 } 276 277 /** 278 * Get the value of the counter. 279 */ 280 public double get() { 281 return value.sum(); 282 } 283 284 private Exemplar getExemplar() { 285 return exemplar.get(); 286 } 287 288 /** 289 * Get the created time of the counter in milliseconds. 290 */ 291 public long created() { 292 return created; 293 } 294 } 295 296 // Convenience methods. 297 298 /** 299 * Increment the counter with no labels by 1. 300 */ 301 public void inc() { 302 inc(1); 303 } 304 305 /** 306 * Like {@link Child#incWithExemplar(String...)}, but for the counter without labels. 307 */ 308 public void incWithExemplar(String... exemplarLabels) { 309 incWithExemplar(1, exemplarLabels); 310 } 311 312 /** 313 * Like {@link Child#incWithExemplar(Map)}, but for the counter without labels. 314 */ 315 public void incWithExemplar(Map<String, String> exemplarLabels) { 316 incWithExemplar(1, exemplarLabels); 317 } 318 319 /** 320 * Increment the counter with no labels by the given amount. 321 * 322 * @throws IllegalArgumentException If amt is negative. 323 */ 324 public void inc(double amt) { 325 noLabelsChild.inc(amt); 326 } 327 328 /** 329 * Like {@link Child#incWithExemplar(double, String...)}, but for the counter without labels. 330 */ 331 public void incWithExemplar(double amt, String... exemplarLabels) { 332 noLabelsChild.incWithExemplar(amt, exemplarLabels); 333 } 334 335 /** 336 * Like {@link Child#incWithExemplar(double, Map)}, but for the counter without labels. 337 */ 338 public void incWithExemplar(double amt, Map<String, String> exemplarLabels) { 339 noLabelsChild.incWithExemplar(amt, exemplarLabels); 340 } 341 342 /** 343 * Get the value of the counter. 344 */ 345 public double get() { 346 return noLabelsChild.get(); 347 } 348 349 @Override 350 public List<MetricFamilySamples> collect() { 351 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(children.size()); 352 for(Map.Entry<List<String>, Child> c: children.entrySet()) { 353 samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get(), c.getValue().getExemplar())); 354 samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0)); 355 } 356 return familySamplesList(Type.COUNTER, samples); 357 } 358 359 @Override 360 public List<MetricFamilySamples> describe() { 361 return Collections.<MetricFamilySamples>singletonList(new CounterMetricFamily(fullname, help, labelNames)); 362 } 363}