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() { 180 this(null, null); 181 } 182 183 public Child(Boolean exemplarsEnabled, CounterExemplarSampler exemplarSampler) { 184 this.exemplarsEnabled = exemplarsEnabled; 185 this.exemplarSampler = exemplarSampler; 186 } 187 188 /** 189 * Increment the counter by 1. 190 */ 191 public void inc() { 192 inc(1); 193 } 194 195 /** 196 * Same as {@link #incWithExemplar(double, String...) incWithExemplar(1, exemplarLabels)}. 197 */ 198 public void incWithExemplar(String... exemplarLabels) { 199 incWithExemplar(1, exemplarLabels); 200 } 201 202 /** 203 * Same as {@link #incWithExemplar(double, Map) incWithExemplar(1, exemplarLabels)}. 204 */ 205 public void incWithExemplar(Map<String, String> exemplarLabels) { 206 incWithExemplar(1, exemplarLabels); 207 } 208 209 /** 210 * Increment the counter by the given amount. 211 * 212 * @throws IllegalArgumentException If amt is negative. 213 */ 214 public void inc(double amt) { 215 incWithExemplar(amt, (String[]) null); 216 } 217 218 /** 219 * Like {@link #inc(double)}, but additionally creates an exemplar. 220 * <p> 221 * This exemplar takes precedence over any exemplar returned by the {@link CounterExemplarSampler} configured 222 * in {@link ExemplarConfig}. 223 * <p> 224 * The exemplar will have {@code amt} as the value, {@code System.currentTimeMillis()} as the timestamp, 225 * and the specified labels. 226 * 227 * @param amt same as in {@link #inc(double)} 228 * @param exemplarLabels list of name/value pairs, as documented in {@link Exemplar#Exemplar(double, String...)}. 229 * A commonly used name is {@code "trace_id"}. 230 * Calling {@code incWithExemplar(amt)} means that an exemplar without labels will be created. 231 * Calling {@code incWithExemplar(amt, (String[]) null)} is equivalent 232 * to calling {@code inc(amt)}. 233 */ 234 public void incWithExemplar(double amt, String... exemplarLabels) { 235 Exemplar exemplar = exemplarLabels == null ? null : new Exemplar(amt, System.currentTimeMillis(), exemplarLabels); 236 if (amt < 0) { 237 throw new IllegalArgumentException("Amount to increment must be non-negative."); 238 } 239 value.add(amt); 240 updateExemplar(amt, exemplar); 241 } 242 243 /** 244 * Same as {@link #incWithExemplar(double, String...)}, but the exemplar labels are passed as a {@link Map}. 245 */ 246 public void incWithExemplar(double amt, Map<String, String> exemplarLabels) { 247 incWithExemplar(amt, Exemplar.mapToArray(exemplarLabels)); 248 } 249 250 private void updateExemplar(double amt, Exemplar userProvidedExemplar) { 251 Exemplar prev, next; 252 do { 253 prev = exemplar.get(); 254 if (userProvidedExemplar == null) { 255 next = sampleNextExemplar(amt, prev); 256 } else { 257 next = userProvidedExemplar; 258 } 259 if (next == null || next == prev) { 260 return; 261 } 262 } while (!exemplar.compareAndSet(prev, next)); 263 } 264 265 private Exemplar sampleNextExemplar(double amt, Exemplar prev) { 266 if (FALSE.equals(exemplarsEnabled)) { 267 return null; 268 } 269 if (exemplarSampler != null) { 270 return exemplarSampler.sample(amt, prev); 271 } 272 if (TRUE.equals(exemplarsEnabled) || ExemplarConfig.isExemplarsEnabled()) { 273 CounterExemplarSampler exemplarSampler = ExemplarConfig.getCounterExemplarSampler(); 274 if (exemplarSampler != null) { 275 return exemplarSampler.sample(amt, prev); 276 } 277 } 278 return null; 279 } 280 281 /** 282 * Get the value of the counter. 283 */ 284 public double get() { 285 return value.sum(); 286 } 287 288 private Exemplar getExemplar() { 289 return exemplar.get(); 290 } 291 292 /** 293 * Get the created time of the counter in milliseconds. 294 */ 295 public long created() { 296 return created; 297 } 298 } 299 300 // Convenience methods. 301 302 /** 303 * Increment the counter with no labels by 1. 304 */ 305 public void inc() { 306 inc(1); 307 } 308 309 /** 310 * Like {@link Child#incWithExemplar(String...)}, but for the counter without labels. 311 */ 312 public void incWithExemplar(String... exemplarLabels) { 313 incWithExemplar(1, exemplarLabels); 314 } 315 316 /** 317 * Like {@link Child#incWithExemplar(Map)}, but for the counter without labels. 318 */ 319 public void incWithExemplar(Map<String, String> exemplarLabels) { 320 incWithExemplar(1, exemplarLabels); 321 } 322 323 /** 324 * Increment the counter with no labels by the given amount. 325 * 326 * @throws IllegalArgumentException If amt is negative. 327 */ 328 public void inc(double amt) { 329 noLabelsChild.inc(amt); 330 } 331 332 /** 333 * Like {@link Child#incWithExemplar(double, String...)}, but for the counter without labels. 334 */ 335 public void incWithExemplar(double amt, String... exemplarLabels) { 336 noLabelsChild.incWithExemplar(amt, exemplarLabels); 337 } 338 339 /** 340 * Like {@link Child#incWithExemplar(double, Map)}, but for the counter without labels. 341 */ 342 public void incWithExemplar(double amt, Map<String, String> exemplarLabels) { 343 noLabelsChild.incWithExemplar(amt, exemplarLabels); 344 } 345 346 /** 347 * Get the value of the counter. 348 */ 349 public double get() { 350 return noLabelsChild.get(); 351 } 352 353 @Override 354 public List<MetricFamilySamples> collect() { 355 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(children.size()); 356 for(Map.Entry<List<String>, Child> c: children.entrySet()) { 357 samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get(), c.getValue().getExemplar())); 358 samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0)); 359 } 360 return familySamplesList(Type.COUNTER, samples); 361 } 362 363 @Override 364 public List<MetricFamilySamples> describe() { 365 return Collections.<MetricFamilySamples>singletonList(new CounterMetricFamily(fullname, help, labelNames)); 366 } 367}