Custom metrics

The custom metrics extension provides an API whereby a developer can create one of six custom metric types for capturing arbitrary metric data. Also, the API provides the ability to create the custom metrics at both the system-level and actor-level. For example, at the actor-level, you could build and use a Counter metric to capture the number of times an actor receives a particular message.

Custom metric types

The custom metric types we currently support are as follows:

  • Counter: provides an integer to increment and decrement.
  • Gauge Double: provides a settable double.
  • Gauge Long: provides a settable long value.
  • Providing Gauge: takes a ValueProvider as the underlying metric.
  • Rate: marks an occurrence for measuring the rate at which it occurs.
  • Recorder: records a long value which can be used to measure distribution.

The underlying behavior of the metric type is defined by the backend used in configuration. For example, if you use chmetrics as your backend, then the underlying metric for a Recorder is a CodaHale Histogram. For more information on backends see Backend Plugins.

Developer API

Accessing CinnamonMetrics

To start using the Cinnamon developer API you will first want to import CinnamonMetrics and the associated metric interfaces:

Scala
import com.lightbend.cinnamon.akka.CinnamonMetrics
import com.lightbend.cinnamon.metric._
Java
import com.lightbend.cinnamon.akka.CinnamonMetrics;
import com.lightbend.cinnamon.metric.*;

Creating system-level custom metrics

System level custom metrics provide an excellent way to capture metric data at the ActorSystem level. To create custom metrics at this level, you use CinnamonMetrics with the ActorSystem and call the appropriate create-method for the desired metric as follows:

Scala
val sysCounter: Counter = CinnamonMetrics(system).createCounter("sysCounter")
val sysGaugeDouble: GaugeDouble = CinnamonMetrics(system).createGaugeDouble("sysGaugeDouble")
val sysGaugeLong: GaugeLong = CinnamonMetrics(system).createGaugeLong("sysGaugeLong")
val sysProvidingGauge: ProvidingGauge = CinnamonMetrics(system).createProvidingGauge("sysProvidingGauge", provider)
val sysRate: Rate = CinnamonMetrics(system).createRate("sysRate")
val sysRecorder: Recorder = CinnamonMetrics(system).createRecorder("sysRecorder")
Java
Counter sysCounter = CinnamonMetrics.get(getSystem()).createCounter("sysCounter");
GaugeDouble sysGaugeDouble = CinnamonMetrics.get(getSystem()).createGaugeDouble("sysGaugeDouble");
GaugeLong sysGaugeLong = CinnamonMetrics.get(getSystem()).createGaugeLong("sysGaugeLong");
ProvidingGauge sysProvidingGauge = CinnamonMetrics.get(getSystem()).createProvidingGauge("sysProvidingGauge", provider);
Rate sysRate = CinnamonMetrics.get(getSystem()).createRate("sysRate");
Recorder sysRecorder = CinnamonMetrics.get(getSystem()).createRecorder("sysRecorder");

Creating actor-level custom metrics

Actor level custom metrics provide an excellent way to capture actor-specific behavior as metric data. To create custom metrics at this level, you use CinnamonMetrics with the ActorContext and call the appropriate create-method for the desired metric as follows:

Scala
val counter: Counter = CinnamonMetrics(context).createCounter("counter")
val gaugeDouble: GaugeDouble = CinnamonMetrics(context).createGaugeDouble("gaugeDouble")
val gaugeLong: GaugeLong = CinnamonMetrics(context).createGaugeLong("gaugeLong")
val providingGauge: ProvidingGauge = CinnamonMetrics(context).createProvidingGauge("providingGauge", provider)
val rate: Rate = CinnamonMetrics(context).createRate("rate")
val recorder: Recorder = CinnamonMetrics(context).createRecorder("recorder")
Java
Counter counter = CinnamonMetrics.get(getContext()).createCounter("counter");
GaugeDouble gaugeDouble = CinnamonMetrics.get(getContext()).createGaugeDouble("gaugeDouble");
GaugeLong gaugeLong = CinnamonMetrics.get(getContext()).createGaugeLong("gaugeLong");
ProvidingGauge providingGauge = CinnamonMetrics.get(getContext()).createProvidingGauge("providingGauge", provider);
Rate rate = CinnamonMetrics.get(getContext()).createRate("rate");
Recorder recorder = CinnamonMetrics.get(getContext()).createRecorder("recorder");

Adding tags to custom metrics

Tags can be added to custom metrics by passing a map of tags to the metric create-method. For example:

Scala
val taggedRecorder: Recorder = CinnamonMetrics(context).createRecorder("recorder", tags = Map("key" -> "value"))
Java
Map<String, String> tags = ImmutableMap.of("key", "value");
Recorder taggedRecorder = CinnamonMetrics.get(getContext()).createRecorder("recorder", tags);

ProvidingGauge value provider

In the examples above you will note that the ProvidingGauge takes an additional parameter, provider. The ProvidingGauge is a special custom metric that takes an external provider of a Long value and reports that value when asked by the underlying metrics backend.

Scala
val provider: ValueProvider = new ValueProvider {
  val cnt: AtomicLong = new AtomicLong(0)
  override def currentValue(): Long = cnt.getAndIncrement()
}
Java
ValueProvider provider = new ValueProvider() {
    AtomicLong cnt = new AtomicLong(0);

    @Override
    public long currentValue() {
        return cnt.getAndIncrement();
    }
};

Using custom metrics

Using the custom metrics involves calling one of the methods supplied by the metric interface as follows:

Scala
counter.increment()

counter.decrement()

gaugeDouble.set(4.2)

gaugeLong.set(12L)

rate.mark()

recorder.record(42L)
Java
counter.increment();

counter.decrement();

gaugeDouble.set(4.2);

gaugeLong.set(12L);

rate.mark();

recorder.record(42L);

Destroying custom metrics

When finished with the metric(s), you should call destroy. Calling destroy will, in turn, invoke the underlying metric backend to run any cleanup tasks if required.

Scala
counter.destroy()

gaugeDouble.destroy()

gaugeLong.destroy()

providingGauge.destroy()

rate.destroy()

recorder.destroy()
Java
counter.destroy();

gaugeDouble.destroy();

gaugeLong.destroy();

providingGauge.destroy();

rate.destroy();

recorder.destroy();

Custom metric timer utility

In addition to the custom metric types, the developer API also provides a timer utility which you can use to time a block of code in nanoseconds. The elapsed time for the code block to run is captured to an underlying Recorder with the name used in the createTimer(...) method.

Scala
val timer: Timer = CinnamonMetrics(context).createTimer("timer")

timer.time { 1 to 10000 sum }
Java
Timer timer = CinnamonMetrics.get(getContext()).createTimer("timer");

timer.time(() -> IntStream.rangeClosed(1, 100000).sum());

For a timer that can be used over asynchronous flows, such as across actors, see the Stopwatch extension.

Custom metric example

Here is an example of actor-level custom metrics by using a Counter to keep track of particular messages that an actor might receive.

import com.lightbend.cinnamon.akka.CinnamonMetrics
import com.lightbend.cinnamon.metric._

case object Init
case object In
case object Out
case object Done

class CustomerCountActor extends Actor {
  val counter: Counter = CinnamonMetrics(context).createCounter("customerCounter")

  def receive: PartialFunction[Any, Unit] = {
    case Init =>
      sender ! Init
    case In =>
      counter.increment()
      sender ! In
    case Out =>
      counter.decrement()
      sender ! Out
    case Done =>
      counter.destroy()
      sender ! Done
      context stop self
  }
}