001package io.prometheus.client; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.List; 006import java.util.Map; 007 008/** 009 * Counter metric, to track counts of events or running totals. 010 * <p> 011 * Example of Counters include: 012 * <ul> 013 * <li>Number of requests processed</li> 014 * <li>Number of items that were inserted into a queue</li> 015 * <li>Total amount of data a system has processed</li> 016 * </ul> 017 * 018 * Counters can only go up (and be reset), if your use case can go down you should use a {@link Gauge} instead. 019 * Use the <code>rate()</code> function in Prometheus to calculate the rate of increase of a Counter. 020 * By convention, the names of Counters are suffixed by <code>_total</code>. 021 * 022 * <p> 023 * An example Counter: 024 * <pre> 025 * {@code 026 * class YourClass { 027 * static final Counter requests = Counter.build() 028 * .name("requests_total").help("Total requests.").register(); 029 * static final Counter failedRequests = Counter.build() 030 * .name("requests_failed_total").help("Total failed requests.").register(); 031 * 032 * void processRequest() { 033 * requests.inc(); 034 * try { 035 * // Your code here. 036 * } catch (Exception e) { 037 * failedRequests.inc(); 038 * throw e; 039 * } 040 * } 041 * } 042 * } 043 * </pre> 044 * 045 * <p> 046 * You can also use labels to track different types of metric: 047 * <pre> 048 * {@code 049 * class YourClass { 050 * static final Counter requests = Counter.build() 051 * .name("requests_total").help("Total requests.") 052 * .labelNames("method").register(); 053 * 054 * void processGetRequest() { 055 * requests.labels("get").inc(); 056 * // Your code here. 057 * } 058 * void processPostRequest() { 059 * requests.labels("post").inc(); 060 * // Your code here. 061 * } 062 * } 063 * } 064 * </pre> 065 * These can be aggregated and processed together much more easily in the Prometheus 066 * server than individual metrics for each labelset. 067 * 068 * If there is a suffix of <code>_total</code> on the metric name, it will be 069 * removed. When exposing the time series for counter value, a 070 * <code>_total</code> suffix will be added. This is for compatibility between 071 * OpenMetrics and the Prometheus text format, as OpenMetrics requires the 072 * <code>_total</code> suffix. 073 */ 074public class Counter extends SimpleCollector<Counter.Child> implements Collector.Describable { 075 076 Counter(Builder b) { 077 super(b); 078 } 079 080 public static class Builder extends SimpleCollector.Builder<Builder, Counter> { 081 @Override 082 public Counter create() { 083 // Gracefully handle pre-OpenMetrics counters. 084 if (name.endsWith("_total")) { 085 name = name.substring(0, name.length() - 6); 086 } 087 return new Counter(this); 088 } 089 } 090 091 /** 092 * Return a Builder to allow configuration of a new Counter. Ensures required fields are provided. 093 * 094 * @param name The name of the metric 095 * @param help The help string of the metric 096 */ 097 public static Builder build(String name, String help) { 098 return new Builder().name(name).help(help); 099 } 100 101 /** 102 * Return a Builder to allow configuration of a new Counter. 103 */ 104 public static Builder build() { 105 return new Builder(); 106 } 107 108 @Override 109 protected Child newChild() { 110 return new Child(); 111 } 112 113 /** 114 * The value of a single Counter. 115 * <p> 116 * <em>Warning:</em> References to a Child become invalid after using 117 * {@link SimpleCollector#remove} or {@link SimpleCollector#clear}, 118 */ 119 public static class Child { 120 private final DoubleAdder value = new DoubleAdder(); 121 private final long created = System.currentTimeMillis(); 122 /** 123 * Increment the counter by 1. 124 */ 125 public void inc() { 126 inc(1); 127 } 128 /** 129 * Increment the counter by the given amount. 130 * @throws IllegalArgumentException If amt is negative. 131 */ 132 public void inc(double amt) { 133 if (amt < 0) { 134 throw new IllegalArgumentException("Amount to increment must be non-negative."); 135 } 136 value.add(amt); 137 } 138 /** 139 * Get the value of the counter. 140 */ 141 public double get() { 142 return value.sum(); 143 } 144 /** 145 * Get the created time of the counter in milliseconds. 146 */ 147 public long created() { 148 return created; 149 } 150 } 151 152 // Convenience methods. 153 /** 154 * Increment the counter with no labels by 1. 155 */ 156 public void inc() { 157 inc(1); 158 } 159 /** 160 * Increment the counter with no labels by the given amount. 161 * @throws IllegalArgumentException If amt is negative. 162 */ 163 public void inc(double amt) { 164 noLabelsChild.inc(amt); 165 } 166 167 /** 168 * Get the value of the counter. 169 */ 170 public double get() { 171 return noLabelsChild.get(); 172 } 173 174 @Override 175 public List<MetricFamilySamples> collect() { 176 List<MetricFamilySamples.Sample> samples = new ArrayList<MetricFamilySamples.Sample>(children.size()); 177 for(Map.Entry<List<String>, Child> c: children.entrySet()) { 178 samples.add(new MetricFamilySamples.Sample(fullname + "_total", labelNames, c.getKey(), c.getValue().get())); 179 samples.add(new MetricFamilySamples.Sample(fullname + "_created", labelNames, c.getKey(), c.getValue().created() / 1000.0)); 180 } 181 return familySamplesList(Type.COUNTER, samples); 182 } 183 184 @Override 185 public List<MetricFamilySamples> describe() { 186 return Collections.<MetricFamilySamples>singletonList(new CounterMetricFamily(fullname, help, labelNames)); 187 } 188}