Java Gauges¶
Polled Gauges¶
The most common use of Gauges is by registering a hook with Spectator, so that it will poll the values in the background. This is done by using the PolledMeter helper class.
A Polled Gauge is registered by passing in an id, a reference to the object, and a function to get or compute a numeric value based on the object. Note that a Gauge should only be registered once, not on each update. Consider this example of a web server tracking the number of connections:
class HttpServer {
// Tracks the number of current connections to the server
private AtomicInteger numConnections;
public HttpServer(Registry registry) {
numConnections = PolledMeter.using(registry)
.withName("server.numConnections")
.monitorValue(new AtomicInteger(0));
}
public void onConnectionCreated() {
numConnections.incrementAndGet();
...
}
public void onConnectionClosed() {
numConnections.decrementAndGet();
...
}
...
}
The Spectator Registry will keep a weak reference to the object. If
the object is garbage collected, then it will automatically drop the registration. In the example
above, the Registry will have a weak reference to numConnections
and the server instance will
have a strong reference to numConnections
. If the server instance goes away, then the Gauge
will as well.
When multiple Gauges are registered with the same id, the reported value will be the sum of the
matches. For example, if multiple instances of the HttpServer
class were created on different
ports, then the value server.numConnections
would be the total number of connections across
all server instances. If a different behavior is desired, then ensure your usage does not perform
multiple registrations.
There are several different ways to register a Gauge:
Using Number¶
A Gauge can also be created based on an implementation of Number. Note the Number implementation should be thread-safe. For example:
AtomicInteger size = new AtomicInteger();
PolledMeter.using(registry)
.withName("queue.size")
.monitorValue(size);
The call will return the Number so the registration can be inline on the assignment:
AtomicInteger size = PolledMeter.using(registry)
.withName("queue.size")
.monitorValue(new AtomicInteger());
Updates to the value are performed by updating the Number instance directly.
Using Lambda¶
Specify a lambda that takes the object as parameter.
public class Queue {
@Inject
public Queue(Registry registry) {
PolledMeter.using(registry)
.withName("queue.size")
.monitorValue(this, Queue::size);
}
...
}
Warning
Be careful to avoid creating a reference to the object in the lambda. It will prevent garbage
collection and can lead to a memory leak in the application. For example, by calling size
without using the passed in object there will be a reference to this
:
PolledMeter.using(registry)
.withName("queue.size")
.monitorValue(this, obj -> size());
Collection Sizes¶
For classes that implement Collection
or Map
, there are helpers:
Queue queue = new LinkedBlockingQueue();
PolledMeter.using(registry)
.withName("queue.size")
.monitorSize(queue);
Map<String, String> cache = new ConcurrentMap<>();
PolledMeter.using(registry)
.withName("cache.size")
.monitorSize(cache);
Monotonic Counters¶
A common technique used by some libraries is to expose a monotonically increasing counter that represents the number of events since the system was initialized. An example of that in the JDK is ThreadPoolExecutor.getCompletedTaskCount, which returns the number of completed tasks on the thread pool.
For sources like this, the monitorMonotonicCounter
method can be used:
// For an implementation of Number
LongAdder tasks = new LongAdder();
PolledMeter.using(registry)
.withName("pool.completedTasks")
.monitorMonotonicCounter(tasks);
// Or using a lambda
ThreadPoolExecutor executor = ...
PolledMeter.using(registry)
.withName("pool.completedTasks")
.monitorMonotonicCounter(executor, ThreadPoolExecutor::getCompletedTaskCount);
For thread pools specifically, there are better options for getting standard metrics. See the docs for the Thread Pools extension for more information.
Active Gauges¶
Gauges can also be set directly by the user. In this mode, the user is responsible for regularly
updating the value of the Gauge by calling set
. Looking at the HttpServer example, with an active
gauge, it would look like:
class HttpServer {
// Tracks the number of current connections to the server
private AtomicInteger numConnections;
private Gauge gauge;
public HttpServer(Registry registry) {
numConnections = new AtomicInteger();
gauge = registry.gauge("server.numConnections");
gauge.set(numConnections.get());
}
public void onConnectionCreated() {
numConnections.incrementAndGet();
gauge.set(numConnections.get());
...
}
public void onConnectionClosed() {
numConnections.decrementAndGet();
gauge.set(numConnections.get());
...
}
...
}