Skip to main content
Version: Current

Custom metrics

This document looks at the different metric types, and how you can use them in your application's code.

Using the MetricService

The MetricService class can be injected into any application code. This class is the entry point for registering and tracking metrics.

There are no additional dependencies to add for using the metric service, for example:

class MetricsSample @Inject constructor(
private val metricService: MetricService,
) {
// more class here...

}

Metrics names and tags

When registering metrics, you should provide a metric name and, optionally, you can provide tags.

The way that the metric is then displayed depends on the reporter that you select.

In addition to the provided tags, the Genesis Platform automatically adds the following tags to all metrics:

  • process name
  • host name
  • process group name

It is recommended that you make your metric names clear and distinct. for example, processing_latency rather than just latency.

In addition to a name, tags can also be provided.

Each tag is a key-value-pair that is reported alongside the metric name.

The syntax for registering all metric types is the same, so we will use counter as an example name here.

Tags can be provided as a vararg or as a map.

val counter = metricService.counter(
"update_queue.message_rate",
"topic" to topic,
)

Types of metric

The Genesis Platform provides support for three types of metric.

TypeExplanationExample
counterTracks a rate, monotonically increasingMessages received
timerMeasures timingMessage latency
gaugeTracks an numerical valueNumber of messages queued
gaugeCounterGauge with a counter-like interfaceNumber of messages queued

Gauges and counters

Gauge or counter?

Use a counter when the rate is interesting. Use a gauge when the number is interesting.

Let us consider an example. When you track the number of messages received, you are usually interested in the number of messages a system handles over an interval, not the total number of messages received since the last restart.

An increase in the rate indicates a higher load on the system.

Conversely, with gauges, the number itself is meaningful; for example, the number of connections available in the connection pool. You don't want to measure how often connections are requested and released in the application. However, you do want to track that connections are always available during application runtime.

Note that a timer also counts the occurrences, so you never need to use a counter and a timer in the same place.

Counter

A counter is used to track an ever-increasing number of specific events. You can think of this as providing a rate over time.

It is generally more interesting to see how many messages have been processed in the last 5 minutes, than it is to see how many messages have been processed since a service was last started.

Example of counters:

  • update queue counter - how many database updates on a table are published every interval
  • message counter - how many messages does a service receive at every interval

Usage

Once a counter has been declared, to use it, just use the increment() function. In this example we will assume that a counter with name myCounter has been declared.

    myCounter.increment()

Timer

Timers are used for two things:

  • to time specific events
  • to count the number of events

So you never need to use both a timer and a counter to track the same event.

Examples of timers are:

  • message latency - how long a service takes to process a specific message
  • query latency - how long a database operation takes

Usage

To use a timer: start the sample first, and then register it on completion.

The example below declares a timer with the name myTimer.

val sample = Timer.start()
doLotsOfWork()
sample.stop(myTimer)

Gauge

Gauges are useful when you are not interested in a rate or a latency, but you need to measure an absolute value that can go either up or down.

For example:

  • queued messages - how many messages are waiting to be processed
  • memory usage - how much memory is available to a service

There is a clear distinction between counters and gauges:

  • Counters are concerned with the rate of events, e.g. the number of messages in an interval
  • With a gauge we are concerned with the absolute value, e.g. what is the number of outstanding messages

Usage

Gauges require a more verbose syntax. You need to register a class, along with a way to extract a value:

// register the gauge when initialising the class
val myTags = HashMap<String, String>()
myTags["TAG"] = "VALUE"
val myGauge = metricService.gauge(
"message_rate",
myTags,
myClass,
MyClass::value
)

// update the value when required
myClass.value = 12.0

GaugeCounter

A gauge counter is a gauge that is set up to behave like a counter. The benefit is that the counter can increase as well as decrease.

Usage

// register the gauge when initialising the class
myGaugeCounter = metricService.gaugeCounter(
"message_rate",
"topic" to "topic",
)

// update the value when required
myGaugeCounter.increment()
// or
myGaugeCounter.decrement()