Efficient open source event counters from the Agrona library.
Agrona
The Agrona library is an open source Java library of utility code. Unlike libraries such as Google Guava or Apache Commons which are general purpose Java utility libraries, Agrona is targeted at providing high performance code. It initially consists of code from the open source Aeron messaging library.
Event Counters
One of the features of the Agrona library is the event counters framework. One of the design goals of Aeron was to be easy to monitor. We wanted to make sure that people could easily check up on what Aeron is doing with services such as Nagios, internally written monitoring software or just from the commandline. Writing integrations with many services is a herculean task in and of itself, but we definitely wanted to be able to expose an API.
We also didn't want to incorporate large 3rd party external dependencies, any allocation heavy code or things we couldn't control the performance of. This meant that we were going to have to write our own event counters rather than using something like the Coda Hale metrics framework. Our requirements for monitoring were very simple though.
- Update or increment the counter's value.
- Read or write to/from the counter value from a different thread or process.
- No garbage creation after initial setup.
- Labels should be associated with each counter value for readability's sake.
Design
Threadsafe updates of a long value is a very simple operation and already supported in Java through the AtomicLong class. The problem with using an AtomicLong as your event counter is that an external program, running in a different process, can't read from the value from your Java heap. Consequently Agrona's event counters are allocated in an off-heap buffer. This can be placed on a memory-mapped file which means it can be shared between two different processes.
In order to give our counters names we store a table of name and counter id entries on another buffer. This can be placed on the same memory mapped file for convenience.
API
The CountersManager
is responsible for allocating event counters. It needs to be instantiated with the buffers upon which to store the event counters and their labels. Here is an example of how to instantiate a counter from the counters manager.
AtomicCounter conductorProxyFails = countersManager.newCounter("Failed offers to DriverConductorProxy");
You can also iterate over the current counter names and their ids. Here is some code that uses that to print a table of the event counter values:
countersManager.forEach((id, label) -> { final int offset = CountersManager.counterOffset(id); final long value = valuesBuffer.getLongVolatile(offset); System.out.format("%3d: %,20d - %s\n", id, value, label); });
Each instance of AtomicCounter
represents one event counter. Here are some examples of using the atomic counter in code.
// Increment the counter in a thread-safe manner conductorProxyFails.increment(); // Increment the counter if you're only writing from a single thread conductorProxyFails.orderedIncrement(); // atomically add 5 to the counter value conductorProxyFails.add(5); // Reset the counter conductorProxyFails.set(0);
Conclusions
I've just gone through a few simple examples of how to use the event counters from Agrona, which hopefully you've found useful. This isn't the only code in Agrona though - there are utilities for agents, and executing timing events as well as collections such as queues, ringbuffers and hashmaps. We're also expanding the library which is already on maven central. Currently documentation is a little bit thin on the ground, but contributions are always welcome.
Thanks to Martin Thompson and Chris West for feedback on this blog post.